Python-checkins
Threads by month
- ----- 2024 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
August 2011
- 7 participants
- 597 discussions
distutils2 (merge default -> default): Merge with changes by Vinay and Alexis
by eric.araujo 31 Aug '11
by eric.araujo 31 Aug '11
31 Aug '11
http://hg.python.org/distutils2/rev/fe1ca017e5ef
changeset: 1127:fe1ca017e5ef
parent: 1126:19f1583d18e4
parent: 1125:4f4cb7e8c0c4
user: Éric Araujo <merwok(a)netwok.org>
date: Sun Aug 28 01:57:22 2011 +0200
summary:
Merge with changes by Vinay and Alexis
files:
distutils2/README | 13 -
distutils2/__init__.py | 6 +-
distutils2/_trove.py | 1088 +++---
distutils2/command/__init__.py | 10 +-
distutils2/command/bdist.py | 29 +-
distutils2/command/bdist_dumb.py | 39 +-
distutils2/command/bdist_msi.py | 121 +-
distutils2/command/bdist_wininst.py | 142 +-
distutils2/command/build.py | 10 +-
distutils2/command/build_clib.py | 61 +-
distutils2/command/build_ext.py | 160 +-
distutils2/command/build_py.py | 55 +-
distutils2/command/build_scripts.py | 93 +-
distutils2/command/check.py | 44 +-
distutils2/command/clean.py | 11 +-
distutils2/command/cmd.py | 96 +-
distutils2/command/command_template | 13 +-
distutils2/command/config.py | 100 +-
distutils2/command/install_data.py | 48 +-
distutils2/command/install_dist.py | 74 +-
distutils2/command/install_distinfo.py | 62 +-
distutils2/command/install_headers.py | 12 +-
distutils2/command/install_lib.py | 38 +-
distutils2/command/install_scripts.py | 23 +-
distutils2/command/register.py | 111 +-
distutils2/command/sdist.py | 109 +-
distutils2/command/test.py | 30 +-
distutils2/command/upload.py | 140 +-
distutils2/command/upload_docs.py | 110 +-
distutils2/command/wininst-10.0-amd64.exe | Bin
distutils2/command/wininst-10.0.exe | Bin
distutils2/compat.py | 20 +-
distutils2/compiler/__init__.py | 75 +-
distutils2/compiler/bcppcompiler.py | 181 +-
distutils2/compiler/ccompiler.py | 192 +-
distutils2/compiler/cygwinccompiler.py | 73 +-
distutils2/compiler/extension.py | 43 +-
distutils2/compiler/msvc9compiler.py | 166 +-
distutils2/compiler/msvccompiler.py | 282 +-
distutils2/compiler/unixccompiler.py | 45 +-
distutils2/config.py | 200 +-
distutils2/create.py | 542 +-
distutils2/database.py | 647 ++++
distutils2/depgraph.py | 125 +-
distutils2/dist.py | 190 +-
distutils2/errors.py | 52 +-
distutils2/fancy_getopt.py | 180 +-
distutils2/install.py | 391 +-
distutils2/manifest.py | 141 +-
distutils2/markers.py | 69 +-
distutils2/metadata.py | 160 +-
distutils2/pypi/__init__.py | 6 +-
distutils2/pypi/base.py | 4 +-
distutils2/pypi/dist.py | 87 +-
distutils2/pypi/errors.py | 22 +-
distutils2/pypi/mirrors.py | 8 +-
distutils2/pypi/simple.py | 183 +-
distutils2/pypi/wrapper.py | 18 +-
distutils2/pypi/xmlrpc.py | 41 +-
distutils2/resources.py | 25 -
distutils2/run.py | 568 ++-
distutils2/tests/__init__.py | 54 +-
distutils2/tests/__main__.py | 23 +
distutils2/tests/fake_dists/babar-0.1.dist-info/INSTALLER | Bin
distutils2/tests/fake_dists/babar-0.1.dist-info/METADATA | 4 +
distutils2/tests/fake_dists/babar-0.1.dist-info/RECORD | Bin
distutils2/tests/fake_dists/babar-0.1.dist-info/REQUESTED | Bin
distutils2/tests/fake_dists/babar-0.1.dist-info/RESOURCES | 2 +
distutils2/tests/fake_dists/babar.cfg | 1 +
distutils2/tests/fake_dists/babar.png | Bin
distutils2/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO | 6 +
distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO | 18 +
distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt | Bin
distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt | 1 +
distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt | 3 +
distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe | 1 +
distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt | 6 +
distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt | Bin
distutils2/tests/fake_dists/cheese-2.0.2.egg-info | 5 +
distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER | Bin
distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA | 9 +
distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD | Bin
distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED | Bin
distutils2/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py | 1 +
distutils2/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py | 10 +
distutils2/tests/fake_dists/choxie-2.0.0.9/truffles.py | 5 +
distutils2/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO | 5 +
distutils2/tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER | Bin
distutils2/tests/fake_dists/grammar-1.0a4.dist-info/METADATA | 5 +
distutils2/tests/fake_dists/grammar-1.0a4.dist-info/RECORD | Bin
distutils2/tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED | Bin
distutils2/tests/fake_dists/grammar-1.0a4/grammar/__init__.py | 1 +
distutils2/tests/fake_dists/grammar-1.0a4/grammar/utils.py | 8 +
distutils2/tests/fake_dists/nut-funkyversion.egg-info | 3 +
distutils2/tests/fake_dists/strawberry-0.6.egg | Bin
distutils2/tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER | Bin
distutils2/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA | 7 +
distutils2/tests/fake_dists/towel_stuff-0.1.dist-info/RECORD | Bin
distutils2/tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED | Bin
distutils2/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py | 18 +
distutils2/tests/fake_dists/truffles-5.0.egg-info | 3 +
distutils2/tests/fixer/fix_idioms.py | 18 +-
distutils2/tests/pypi_server.py | 96 +-
distutils2/tests/pypi_test_server.py | 59 +
distutils2/tests/pypiserver/downloads_with_md5/packages/source/f/foobar/foobar-0.1.tar.gz | Bin
distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/foobar-0.1.tar.gz | Bin
distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/index.html | 2 +-
distutils2/tests/support.py | 433 ++-
distutils2/tests/test_ccompiler.py | 8 +-
distutils2/tests/test_command_bdist.py | 6 +-
distutils2/tests/test_command_bdist_dumb.py | 36 +-
distutils2/tests/test_command_bdist_msi.py | 7 +-
distutils2/tests/test_command_bdist_wininst.py | 9 +-
distutils2/tests/test_command_build.py | 6 +-
distutils2/tests/test_command_build_clib.py | 43 +-
distutils2/tests/test_command_build_ext.py | 272 +-
distutils2/tests/test_command_build_py.py | 34 +-
distutils2/tests/test_command_build_scripts.py | 23 +-
distutils2/tests/test_command_check.py | 78 +-
distutils2/tests/test_command_clean.py | 15 +-
distutils2/tests/test_command_cmd.py | 31 +-
distutils2/tests/test_command_config.py | 23 +-
distutils2/tests/test_command_install_data.py | 45 +-
distutils2/tests/test_command_install_dist.py | 52 +-
distutils2/tests/test_command_install_distinfo.py | 52 +-
distutils2/tests/test_command_install_headers.py | 5 +-
distutils2/tests/test_command_install_lib.py | 47 +-
distutils2/tests/test_command_install_scripts.py | 29 +-
distutils2/tests/test_command_register.py | 95 +-
distutils2/tests/test_command_sdist.py | 177 +-
distutils2/tests/test_command_test.py | 105 +-
distutils2/tests/test_command_upload.py | 54 +-
distutils2/tests/test_command_upload_docs.py | 116 +-
distutils2/tests/test_compiler.py | 16 +-
distutils2/tests/test_config.py | 248 +-
distutils2/tests/test_create.py | 280 +-
distutils2/tests/test_cygwinccompiler.py | 27 +-
distutils2/tests/test_database.py | 674 ++++
distutils2/tests/test_depgraph.py | 124 +-
distutils2/tests/test_dist.py | 198 +-
distutils2/tests/test_extension.py | 2 +-
distutils2/tests/test_install.py | 177 +-
distutils2/tests/test_manifest.py | 80 +-
distutils2/tests/test_markers.py | 16 +-
distutils2/tests/test_metadata.py | 75 +-
distutils2/tests/test_mixin2to3.py | 90 +-
distutils2/tests/test_msvc9compiler.py | 43 +-
distutils2/tests/test_pypi_dist.py | 81 +-
distutils2/tests/test_pypi_server.py | 80 +-
distutils2/tests/test_pypi_simple.py | 145 +-
distutils2/tests/test_pypi_xmlrpc.py | 56 +-
distutils2/tests/test_resources.py | 174 -
distutils2/tests/test_run.py | 40 +-
distutils2/tests/test_uninstall.py | 99 +-
distutils2/tests/test_unixccompiler.py | 25 +-
distutils2/tests/test_util.py | 551 +++-
distutils2/tests/test_version.py | 19 +-
distutils2/tests/xxmodule.c | 379 --
distutils2/util.py | 1447 ++++++++-
distutils2/version.py | 16 +-
scripts/pysetup | 4 +
setup.py | 6 +-
sysconfig.cfg | 111 +
sysconfig.py | 766 +++++
test_distutils2.py | 5 +
165 files changed, 9733 insertions(+), 6080 deletions(-)
diff --git a/distutils2/README b/distutils2/README
deleted file mode 100644
--- a/distutils2/README
+++ /dev/null
@@ -1,13 +0,0 @@
-This directory contains the Distutils2 package.
-
-There's a full documentation available at:
-
- http://docs.python.org/distutils/
-
-The Distutils-SIG web page is also a good starting point:
-
- http://www.python.org/sigs/distutils-sig/
-
-WARNING: Distutils2 must remain compatible with Python 2.4
-
-$Id: README 70017 2009-02-27 12:53:34Z tarek.ziade $
diff --git a/distutils2/__init__.py b/distutils2/__init__.py
--- a/distutils2/__init__.py
+++ b/distutils2/__init__.py
@@ -1,12 +1,14 @@
-"""distutils
+"""Support for distutils2, distribution and installation of Python projects.
-Third-party tools can use parts of Distutils2 as building blocks
+Third-party tools can use parts of distutils2 as building blocks
without causing the other modules to be imported:
import distutils2.version
+ import distutils2.metadata
import distutils2.pypi.simple
import distutils2.tests.pypi_server
"""
+
from logging import getLogger
__all__ = ['__version__', 'logger']
diff --git a/distutils2/_trove.py b/distutils2/_trove.py
--- a/distutils2/_trove.py
+++ b/distutils2/_trove.py
@@ -1,4 +1,4 @@
-# Temporary helper for mkcfg.
+"""Temporary helper for create."""
# XXX get the list from PyPI and cache it instead of hardcoding
@@ -6,547 +6,547 @@
# than a list of strings
all_classifiers = [
- 'Development Status :: 1 - Planning',
- 'Development Status :: 2 - Pre-Alpha',
- 'Development Status :: 3 - Alpha',
- 'Development Status :: 4 - Beta',
- 'Development Status :: 5 - Production/Stable',
- 'Development Status :: 6 - Mature',
- 'Development Status :: 7 - Inactive',
- 'Environment :: Console',
- 'Environment :: Console :: Curses',
- 'Environment :: Console :: Framebuffer',
- 'Environment :: Console :: Newt',
- 'Environment :: Console :: svgalib',
- "Environment :: Handhelds/PDA's",
- 'Environment :: MacOS X',
- 'Environment :: MacOS X :: Aqua',
- 'Environment :: MacOS X :: Carbon',
- 'Environment :: MacOS X :: Cocoa',
- 'Environment :: No Input/Output (Daemon)',
- 'Environment :: Other Environment',
- 'Environment :: Plugins',
- 'Environment :: Web Environment',
- 'Environment :: Web Environment :: Buffet',
- 'Environment :: Web Environment :: Mozilla',
- 'Environment :: Web Environment :: ToscaWidgets',
- 'Environment :: Win32 (MS Windows)',
- 'Environment :: X11 Applications',
- 'Environment :: X11 Applications :: Gnome',
- 'Environment :: X11 Applications :: GTK',
- 'Environment :: X11 Applications :: KDE',
- 'Environment :: X11 Applications :: Qt',
- 'Framework :: BFG',
- 'Framework :: Buildout',
- 'Framework :: Chandler',
- 'Framework :: CubicWeb',
- 'Framework :: Django',
- 'Framework :: IDLE',
- 'Framework :: Paste',
- 'Framework :: Plone',
- 'Framework :: Pylons',
- 'Framework :: Setuptools Plugin',
- 'Framework :: Trac',
- 'Framework :: TurboGears',
- 'Framework :: TurboGears :: Applications',
- 'Framework :: TurboGears :: Widgets',
- 'Framework :: Twisted',
- 'Framework :: ZODB',
- 'Framework :: Zope2',
- 'Framework :: Zope3',
- 'Intended Audience :: Customer Service',
- 'Intended Audience :: Developers',
- 'Intended Audience :: Education',
- 'Intended Audience :: End Users/Desktop',
- 'Intended Audience :: Financial and Insurance Industry',
- 'Intended Audience :: Healthcare Industry',
- 'Intended Audience :: Information Technology',
- 'Intended Audience :: Legal Industry',
- 'Intended Audience :: Manufacturing',
- 'Intended Audience :: Other Audience',
- 'Intended Audience :: Religion',
- 'Intended Audience :: Science/Research',
- 'Intended Audience :: System Administrators',
- 'Intended Audience :: Telecommunications Industry',
- 'License :: Aladdin Free Public License (AFPL)',
- 'License :: DFSG approved',
- 'License :: Eiffel Forum License (EFL)',
- 'License :: Free For Educational Use',
- 'License :: Free For Home Use',
- 'License :: Free for non-commercial use',
- 'License :: Freely Distributable',
- 'License :: Free To Use But Restricted',
- 'License :: Freeware',
- 'License :: Netscape Public License (NPL)',
- 'License :: Nokia Open Source License (NOKOS)',
- 'License :: OSI Approved',
- 'License :: OSI Approved :: Academic Free License (AFL)',
- 'License :: OSI Approved :: Apache Software License',
- 'License :: OSI Approved :: Apple Public Source License',
- 'License :: OSI Approved :: Artistic License',
- 'License :: OSI Approved :: Attribution Assurance License',
- 'License :: OSI Approved :: BSD License',
- 'License :: OSI Approved :: Common Public License',
- 'License :: OSI Approved :: Eiffel Forum License',
- 'License :: OSI Approved :: European Union Public Licence 1.0 (EUPL 1.0)',
- 'License :: OSI Approved :: European Union Public Licence 1.1 (EUPL 1.1)',
- 'License :: OSI Approved :: GNU Affero General Public License v3',
- 'License :: OSI Approved :: GNU Free Documentation License (FDL)',
- 'License :: OSI Approved :: GNU General Public License (GPL)',
- 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
- 'License :: OSI Approved :: IBM Public License',
- 'License :: OSI Approved :: Intel Open Source License',
- 'License :: OSI Approved :: ISC License (ISCL)',
- 'License :: OSI Approved :: Jabber Open Source License',
- 'License :: OSI Approved :: MIT License',
- 'License :: OSI Approved :: MITRE Collaborative Virtual Workspace License (CVW)',
- 'License :: OSI Approved :: Motosoto License',
- 'License :: OSI Approved :: Mozilla Public License 1.0 (MPL)',
- 'License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)',
- 'License :: OSI Approved :: Nethack General Public License',
- 'License :: OSI Approved :: Nokia Open Source License',
- 'License :: OSI Approved :: Open Group Test Suite License',
- 'License :: OSI Approved :: Python License (CNRI Python License)',
- 'License :: OSI Approved :: Python Software Foundation License',
- 'License :: OSI Approved :: Qt Public License (QPL)',
- 'License :: OSI Approved :: Ricoh Source Code Public License',
- 'License :: OSI Approved :: Sleepycat License',
- 'License :: OSI Approved :: Sun Industry Standards Source License (SISSL)',
- 'License :: OSI Approved :: Sun Public License',
- 'License :: OSI Approved :: University of Illinois/NCSA Open Source License',
- 'License :: OSI Approved :: Vovida Software License 1.0',
- 'License :: OSI Approved :: W3C License',
- 'License :: OSI Approved :: X.Net License',
- 'License :: OSI Approved :: zlib/libpng License',
- 'License :: OSI Approved :: Zope Public License',
- 'License :: Other/Proprietary License',
- 'License :: Public Domain',
- 'License :: Repoze Public License',
- 'Natural Language :: Afrikaans',
- 'Natural Language :: Arabic',
- 'Natural Language :: Bengali',
- 'Natural Language :: Bosnian',
- 'Natural Language :: Bulgarian',
- 'Natural Language :: Catalan',
- 'Natural Language :: Chinese (Simplified)',
- 'Natural Language :: Chinese (Traditional)',
- 'Natural Language :: Croatian',
- 'Natural Language :: Czech',
- 'Natural Language :: Danish',
- 'Natural Language :: Dutch',
- 'Natural Language :: English',
- 'Natural Language :: Esperanto',
- 'Natural Language :: Finnish',
- 'Natural Language :: French',
- 'Natural Language :: German',
- 'Natural Language :: Greek',
- 'Natural Language :: Hebrew',
- 'Natural Language :: Hindi',
- 'Natural Language :: Hungarian',
- 'Natural Language :: Icelandic',
- 'Natural Language :: Indonesian',
- 'Natural Language :: Italian',
- 'Natural Language :: Japanese',
- 'Natural Language :: Javanese',
- 'Natural Language :: Korean',
- 'Natural Language :: Latin',
- 'Natural Language :: Latvian',
- 'Natural Language :: Macedonian',
- 'Natural Language :: Malay',
- 'Natural Language :: Marathi',
- 'Natural Language :: Norwegian',
- 'Natural Language :: Panjabi',
- 'Natural Language :: Persian',
- 'Natural Language :: Polish',
- 'Natural Language :: Portuguese',
- 'Natural Language :: Portuguese (Brazilian)',
- 'Natural Language :: Romanian',
- 'Natural Language :: Russian',
- 'Natural Language :: Serbian',
- 'Natural Language :: Slovak',
- 'Natural Language :: Slovenian',
- 'Natural Language :: Spanish',
- 'Natural Language :: Swedish',
- 'Natural Language :: Tamil',
- 'Natural Language :: Telugu',
- 'Natural Language :: Thai',
- 'Natural Language :: Turkish',
- 'Natural Language :: Ukranian',
- 'Natural Language :: Urdu',
- 'Natural Language :: Vietnamese',
- 'Operating System :: BeOS',
- 'Operating System :: MacOS',
- 'Operating System :: MacOS :: MacOS 9',
- 'Operating System :: MacOS :: MacOS X',
- 'Operating System :: Microsoft',
- 'Operating System :: Microsoft :: MS-DOS',
- 'Operating System :: Microsoft :: Windows',
- 'Operating System :: Microsoft :: Windows :: Windows 3.1 or Earlier',
- 'Operating System :: Microsoft :: Windows :: Windows 95/98/2000',
- 'Operating System :: Microsoft :: Windows :: Windows CE',
- 'Operating System :: Microsoft :: Windows :: Windows NT/2000',
- 'Operating System :: OS/2',
- 'Operating System :: OS Independent',
- 'Operating System :: Other OS',
- 'Operating System :: PalmOS',
- 'Operating System :: PDA Systems',
- 'Operating System :: POSIX',
- 'Operating System :: POSIX :: AIX',
- 'Operating System :: POSIX :: BSD',
- 'Operating System :: POSIX :: BSD :: BSD/OS',
- 'Operating System :: POSIX :: BSD :: FreeBSD',
- 'Operating System :: POSIX :: BSD :: NetBSD',
- 'Operating System :: POSIX :: BSD :: OpenBSD',
- 'Operating System :: POSIX :: GNU Hurd',
- 'Operating System :: POSIX :: HP-UX',
- 'Operating System :: POSIX :: IRIX',
- 'Operating System :: POSIX :: Linux',
- 'Operating System :: POSIX :: Other',
- 'Operating System :: POSIX :: SCO',
- 'Operating System :: POSIX :: SunOS/Solaris',
- 'Operating System :: Unix',
- 'Programming Language :: Ada',
- 'Programming Language :: APL',
- 'Programming Language :: ASP',
- 'Programming Language :: Assembly',
- 'Programming Language :: Awk',
- 'Programming Language :: Basic',
- 'Programming Language :: C',
- 'Programming Language :: C#',
- 'Programming Language :: C++',
- 'Programming Language :: Cold Fusion',
- 'Programming Language :: Cython',
- 'Programming Language :: Delphi/Kylix',
- 'Programming Language :: Dylan',
- 'Programming Language :: Eiffel',
- 'Programming Language :: Emacs-Lisp',
- 'Programming Language :: Erlang',
- 'Programming Language :: Euler',
- 'Programming Language :: Euphoria',
- 'Programming Language :: Forth',
- 'Programming Language :: Fortran',
- 'Programming Language :: Haskell',
- 'Programming Language :: Java',
- 'Programming Language :: JavaScript',
- 'Programming Language :: Lisp',
- 'Programming Language :: Logo',
- 'Programming Language :: ML',
- 'Programming Language :: Modula',
- 'Programming Language :: Objective C',
- 'Programming Language :: Object Pascal',
- 'Programming Language :: OCaml',
- 'Programming Language :: Other',
- 'Programming Language :: Other Scripting Engines',
- 'Programming Language :: Pascal',
- 'Programming Language :: Perl',
- 'Programming Language :: PHP',
- 'Programming Language :: Pike',
- 'Programming Language :: Pliant',
- 'Programming Language :: PL/SQL',
- 'Programming Language :: PROGRESS',
- 'Programming Language :: Prolog',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.3',
- '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.0',
- 'Programming Language :: Python :: 3.1',
- 'Programming Language :: Python :: 3.2',
- 'Programming Language :: REBOL',
- 'Programming Language :: Rexx',
- 'Programming Language :: Ruby',
- 'Programming Language :: Scheme',
- 'Programming Language :: Simula',
- 'Programming Language :: Smalltalk',
- 'Programming Language :: SQL',
- 'Programming Language :: Tcl',
- 'Programming Language :: Unix Shell',
- 'Programming Language :: Visual Basic',
- 'Programming Language :: XBasic',
- 'Programming Language :: YACC',
- 'Programming Language :: Zope',
- 'Topic :: Adaptive Technologies',
- 'Topic :: Artistic Software',
- 'Topic :: Communications',
- 'Topic :: Communications :: BBS',
- 'Topic :: Communications :: Chat',
- 'Topic :: Communications :: Chat :: AOL Instant Messenger',
- 'Topic :: Communications :: Chat :: ICQ',
- 'Topic :: Communications :: Chat :: Internet Relay Chat',
- 'Topic :: Communications :: Chat :: Unix Talk',
- 'Topic :: Communications :: Conferencing',
- 'Topic :: Communications :: Email',
- 'Topic :: Communications :: Email :: Address Book',
- 'Topic :: Communications :: Email :: Email Clients (MUA)',
- 'Topic :: Communications :: Email :: Filters',
- 'Topic :: Communications :: Email :: Mailing List Servers',
- 'Topic :: Communications :: Email :: Mail Transport Agents',
- 'Topic :: Communications :: Email :: Post-Office',
- 'Topic :: Communications :: Email :: Post-Office :: IMAP',
- 'Topic :: Communications :: Email :: Post-Office :: POP3',
- 'Topic :: Communications :: Fax',
- 'Topic :: Communications :: FIDO',
- 'Topic :: Communications :: File Sharing',
- 'Topic :: Communications :: File Sharing :: Gnutella',
- 'Topic :: Communications :: File Sharing :: Napster',
- 'Topic :: Communications :: Ham Radio',
- 'Topic :: Communications :: Internet Phone',
- 'Topic :: Communications :: Telephony',
- 'Topic :: Communications :: Usenet News',
- 'Topic :: Database',
- 'Topic :: Database :: Database Engines/Servers',
- 'Topic :: Database :: Front-Ends',
- 'Topic :: Desktop Environment',
- 'Topic :: Desktop Environment :: File Managers',
- 'Topic :: Desktop Environment :: Gnome',
- 'Topic :: Desktop Environment :: GNUstep',
- 'Topic :: Desktop Environment :: K Desktop Environment (KDE)',
- 'Topic :: Desktop Environment :: K Desktop Environment (KDE) :: Themes',
- 'Topic :: Desktop Environment :: PicoGUI',
- 'Topic :: Desktop Environment :: PicoGUI :: Applications',
- 'Topic :: Desktop Environment :: PicoGUI :: Themes',
- 'Topic :: Desktop Environment :: Screen Savers',
- 'Topic :: Desktop Environment :: Window Managers',
- 'Topic :: Desktop Environment :: Window Managers :: Afterstep',
- 'Topic :: Desktop Environment :: Window Managers :: Afterstep :: Themes',
- 'Topic :: Desktop Environment :: Window Managers :: Applets',
- 'Topic :: Desktop Environment :: Window Managers :: Blackbox',
- 'Topic :: Desktop Environment :: Window Managers :: Blackbox :: Themes',
- 'Topic :: Desktop Environment :: Window Managers :: CTWM',
- 'Topic :: Desktop Environment :: Window Managers :: CTWM :: Themes',
- 'Topic :: Desktop Environment :: Window Managers :: Enlightenment',
- 'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Epplets',
- 'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR15',
- 'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR16',
- 'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR17',
- 'Topic :: Desktop Environment :: Window Managers :: Fluxbox',
- 'Topic :: Desktop Environment :: Window Managers :: Fluxbox :: Themes',
- 'Topic :: Desktop Environment :: Window Managers :: FVWM',
- 'Topic :: Desktop Environment :: Window Managers :: FVWM :: Themes',
- 'Topic :: Desktop Environment :: Window Managers :: IceWM',
- 'Topic :: Desktop Environment :: Window Managers :: IceWM :: Themes',
- 'Topic :: Desktop Environment :: Window Managers :: MetaCity',
- 'Topic :: Desktop Environment :: Window Managers :: MetaCity :: Themes',
- 'Topic :: Desktop Environment :: Window Managers :: Oroborus',
- 'Topic :: Desktop Environment :: Window Managers :: Oroborus :: Themes',
- 'Topic :: Desktop Environment :: Window Managers :: Sawfish',
- 'Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes 0.30',
- 'Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes pre-0.30',
- 'Topic :: Desktop Environment :: Window Managers :: Waimea',
- 'Topic :: Desktop Environment :: Window Managers :: Waimea :: Themes',
- 'Topic :: Desktop Environment :: Window Managers :: Window Maker',
- 'Topic :: Desktop Environment :: Window Managers :: Window Maker :: Applets',
- 'Topic :: Desktop Environment :: Window Managers :: Window Maker :: Themes',
- 'Topic :: Desktop Environment :: Window Managers :: XFCE',
- 'Topic :: Desktop Environment :: Window Managers :: XFCE :: Themes',
- 'Topic :: Documentation',
- 'Topic :: Education',
- 'Topic :: Education :: Computer Aided Instruction (CAI)',
- 'Topic :: Education :: Testing',
- 'Topic :: Games/Entertainment',
- 'Topic :: Games/Entertainment :: Arcade',
- 'Topic :: Games/Entertainment :: Board Games',
- 'Topic :: Games/Entertainment :: First Person Shooters',
- 'Topic :: Games/Entertainment :: Fortune Cookies',
- 'Topic :: Games/Entertainment :: Multi-User Dungeons (MUD)',
- 'Topic :: Games/Entertainment :: Puzzle Games',
- 'Topic :: Games/Entertainment :: Real Time Strategy',
- 'Topic :: Games/Entertainment :: Role-Playing',
- 'Topic :: Games/Entertainment :: Side-Scrolling/Arcade Games',
- 'Topic :: Games/Entertainment :: Simulation',
- 'Topic :: Games/Entertainment :: Turn Based Strategy',
- 'Topic :: Home Automation',
- 'Topic :: Internet',
- 'Topic :: Internet :: File Transfer Protocol (FTP)',
- 'Topic :: Internet :: Finger',
- 'Topic :: Internet :: Log Analysis',
- 'Topic :: Internet :: Name Service (DNS)',
- 'Topic :: Internet :: Proxy Servers',
- 'Topic :: Internet :: WAP',
- 'Topic :: Internet :: WWW/HTTP',
- 'Topic :: Internet :: WWW/HTTP :: Browsers',
- 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
- 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries',
- 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Message Boards',
- 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: News/Diary',
- 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Page Counters',
- 'Topic :: Internet :: WWW/HTTP :: HTTP Servers',
- 'Topic :: Internet :: WWW/HTTP :: Indexing/Search',
- 'Topic :: Internet :: WWW/HTTP :: Site Management',
- 'Topic :: Internet :: WWW/HTTP :: Site Management :: Link Checking',
- 'Topic :: Internet :: WWW/HTTP :: WSGI',
- 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
- 'Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware',
- 'Topic :: Internet :: WWW/HTTP :: WSGI :: Server',
- 'Topic :: Internet :: Z39.50',
- 'Topic :: Multimedia',
- 'Topic :: Multimedia :: Graphics',
- 'Topic :: Multimedia :: Graphics :: 3D Modeling',
- 'Topic :: Multimedia :: Graphics :: 3D Rendering',
- 'Topic :: Multimedia :: Graphics :: Capture',
- 'Topic :: Multimedia :: Graphics :: Capture :: Digital Camera',
- 'Topic :: Multimedia :: Graphics :: Capture :: Scanners',
- 'Topic :: Multimedia :: Graphics :: Capture :: Screen Capture',
- 'Topic :: Multimedia :: Graphics :: Editors',
- 'Topic :: Multimedia :: Graphics :: Editors :: Raster-Based',
- 'Topic :: Multimedia :: Graphics :: Editors :: Vector-Based',
- 'Topic :: Multimedia :: Graphics :: Graphics Conversion',
- 'Topic :: Multimedia :: Graphics :: Presentation',
- 'Topic :: Multimedia :: Graphics :: Viewers',
- 'Topic :: Multimedia :: Sound/Audio',
- 'Topic :: Multimedia :: Sound/Audio :: Analysis',
- 'Topic :: Multimedia :: Sound/Audio :: Capture/Recording',
- 'Topic :: Multimedia :: Sound/Audio :: CD Audio',
- 'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Playing',
- 'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Ripping',
- 'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Writing',
- 'Topic :: Multimedia :: Sound/Audio :: Conversion',
- 'Topic :: Multimedia :: Sound/Audio :: Editors',
- 'Topic :: Multimedia :: Sound/Audio :: MIDI',
- 'Topic :: Multimedia :: Sound/Audio :: Mixers',
- 'Topic :: Multimedia :: Sound/Audio :: Players',
- 'Topic :: Multimedia :: Sound/Audio :: Players :: MP3',
- 'Topic :: Multimedia :: Sound/Audio :: Sound Synthesis',
- 'Topic :: Multimedia :: Sound/Audio :: Speech',
- 'Topic :: Multimedia :: Video',
- 'Topic :: Multimedia :: Video :: Capture',
- 'Topic :: Multimedia :: Video :: Conversion',
- 'Topic :: Multimedia :: Video :: Display',
- 'Topic :: Multimedia :: Video :: Non-Linear Editor',
- 'Topic :: Office/Business',
- 'Topic :: Office/Business :: Financial',
- 'Topic :: Office/Business :: Financial :: Accounting',
- 'Topic :: Office/Business :: Financial :: Investment',
- 'Topic :: Office/Business :: Financial :: Point-Of-Sale',
- 'Topic :: Office/Business :: Financial :: Spreadsheet',
- 'Topic :: Office/Business :: Groupware',
- 'Topic :: Office/Business :: News/Diary',
- 'Topic :: Office/Business :: Office Suites',
- 'Topic :: Office/Business :: Scheduling',
- 'Topic :: Other/Nonlisted Topic',
- 'Topic :: Printing',
- 'Topic :: Religion',
- 'Topic :: Scientific/Engineering',
- 'Topic :: Scientific/Engineering :: Artificial Intelligence',
- 'Topic :: Scientific/Engineering :: Astronomy',
- 'Topic :: Scientific/Engineering :: Atmospheric Science',
- 'Topic :: Scientific/Engineering :: Bio-Informatics',
- 'Topic :: Scientific/Engineering :: Chemistry',
- 'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)',
- 'Topic :: Scientific/Engineering :: GIS',
- 'Topic :: Scientific/Engineering :: Human Machine Interfaces',
- 'Topic :: Scientific/Engineering :: Image Recognition',
- 'Topic :: Scientific/Engineering :: Information Analysis',
- 'Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator',
- 'Topic :: Scientific/Engineering :: Mathematics',
- 'Topic :: Scientific/Engineering :: Medical Science Apps.',
- 'Topic :: Scientific/Engineering :: Physics',
- 'Topic :: Scientific/Engineering :: Visualization',
- 'Topic :: Security',
- 'Topic :: Security :: Cryptography',
- 'Topic :: Sociology',
- 'Topic :: Sociology :: Genealogy',
- 'Topic :: Sociology :: History',
- 'Topic :: Software Development',
- 'Topic :: Software Development :: Assemblers',
- 'Topic :: Software Development :: Bug Tracking',
- 'Topic :: Software Development :: Build Tools',
- 'Topic :: Software Development :: Code Generators',
- 'Topic :: Software Development :: Compilers',
- 'Topic :: Software Development :: Debuggers',
- 'Topic :: Software Development :: Disassemblers',
- 'Topic :: Software Development :: Documentation',
- 'Topic :: Software Development :: Embedded Systems',
- 'Topic :: Software Development :: Internationalization',
- 'Topic :: Software Development :: Interpreters',
- 'Topic :: Software Development :: Libraries',
- 'Topic :: Software Development :: Libraries :: Application Frameworks',
- 'Topic :: Software Development :: Libraries :: Java Libraries',
- 'Topic :: Software Development :: Libraries :: Perl Modules',
- 'Topic :: Software Development :: Libraries :: PHP Classes',
- 'Topic :: Software Development :: Libraries :: Pike Modules',
- 'Topic :: Software Development :: Libraries :: pygame',
- 'Topic :: Software Development :: Libraries :: Python Modules',
- 'Topic :: Software Development :: Libraries :: Ruby Modules',
- 'Topic :: Software Development :: Libraries :: Tcl Extensions',
- 'Topic :: Software Development :: Localization',
- 'Topic :: Software Development :: Object Brokering',
- 'Topic :: Software Development :: Object Brokering :: CORBA',
- 'Topic :: Software Development :: Pre-processors',
- 'Topic :: Software Development :: Quality Assurance',
- 'Topic :: Software Development :: Testing',
- 'Topic :: Software Development :: Testing :: Traffic Generation',
- 'Topic :: Software Development :: User Interfaces',
- 'Topic :: Software Development :: Version Control',
- 'Topic :: Software Development :: Version Control :: CVS',
- 'Topic :: Software Development :: Version Control :: RCS',
- 'Topic :: Software Development :: Version Control :: SCCS',
- 'Topic :: Software Development :: Widget Sets',
- 'Topic :: System',
- 'Topic :: System :: Archiving',
- 'Topic :: System :: Archiving :: Backup',
- 'Topic :: System :: Archiving :: Compression',
- 'Topic :: System :: Archiving :: Mirroring',
- 'Topic :: System :: Archiving :: Packaging',
- 'Topic :: System :: Benchmark',
- 'Topic :: System :: Boot',
- 'Topic :: System :: Boot :: Init',
- 'Topic :: System :: Clustering',
- 'Topic :: System :: Console Fonts',
- 'Topic :: System :: Distributed Computing',
- 'Topic :: System :: Emulators',
- 'Topic :: System :: Filesystems',
- 'Topic :: System :: Hardware',
- 'Topic :: System :: Hardware :: Hardware Drivers',
- 'Topic :: System :: Hardware :: Mainframes',
- 'Topic :: System :: Hardware :: Symmetric Multi-processing',
- 'Topic :: System :: Installation/Setup',
- 'Topic :: System :: Logging',
- 'Topic :: System :: Monitoring',
- 'Topic :: System :: Networking',
- 'Topic :: System :: Networking :: Firewalls',
- 'Topic :: System :: Networking :: Monitoring',
- 'Topic :: System :: Networking :: Monitoring :: Hardware Watchdog',
- 'Topic :: System :: Networking :: Time Synchronization',
- 'Topic :: System :: Operating System',
- 'Topic :: System :: Operating System Kernels',
- 'Topic :: System :: Operating System Kernels :: BSD',
- 'Topic :: System :: Operating System Kernels :: GNU Hurd',
- 'Topic :: System :: Operating System Kernels :: Linux',
- 'Topic :: System :: Power (UPS)',
- 'Topic :: System :: Recovery Tools',
- 'Topic :: System :: Shells',
- 'Topic :: System :: Software Distribution',
- 'Topic :: System :: Systems Administration',
- 'Topic :: System :: Systems Administration :: Authentication/Directory',
- 'Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP',
- 'Topic :: System :: Systems Administration :: Authentication/Directory :: NIS',
- 'Topic :: System :: System Shells',
- 'Topic :: Terminals',
- 'Topic :: Terminals :: Serial',
- 'Topic :: Terminals :: Telnet',
- 'Topic :: Terminals :: Terminal Emulators/X Terminals',
- 'Topic :: Text Editors',
- 'Topic :: Text Editors :: Documentation',
- 'Topic :: Text Editors :: Emacs',
- 'Topic :: Text Editors :: Integrated Development Environments (IDE)',
- 'Topic :: Text Editors :: Text Processing',
- 'Topic :: Text Editors :: Word Processors',
- 'Topic :: Text Processing',
- 'Topic :: Text Processing :: Filters',
- 'Topic :: Text Processing :: Fonts',
- 'Topic :: Text Processing :: General',
- 'Topic :: Text Processing :: Indexing',
- 'Topic :: Text Processing :: Linguistic',
- 'Topic :: Text Processing :: Markup',
- 'Topic :: Text Processing :: Markup :: HTML',
- 'Topic :: Text Processing :: Markup :: LaTeX',
- 'Topic :: Text Processing :: Markup :: SGML',
- 'Topic :: Text Processing :: Markup :: VRML',
- 'Topic :: Text Processing :: Markup :: XML',
- 'Topic :: Utilities',
+'Development Status :: 1 - Planning',
+'Development Status :: 2 - Pre-Alpha',
+'Development Status :: 3 - Alpha',
+'Development Status :: 4 - Beta',
+'Development Status :: 5 - Production/Stable',
+'Development Status :: 6 - Mature',
+'Development Status :: 7 - Inactive',
+'Environment :: Console',
+'Environment :: Console :: Curses',
+'Environment :: Console :: Framebuffer',
+'Environment :: Console :: Newt',
+'Environment :: Console :: svgalib',
+"Environment :: Handhelds/PDA's",
+'Environment :: MacOS X',
+'Environment :: MacOS X :: Aqua',
+'Environment :: MacOS X :: Carbon',
+'Environment :: MacOS X :: Cocoa',
+'Environment :: No Input/Output (Daemon)',
+'Environment :: Other Environment',
+'Environment :: Plugins',
+'Environment :: Web Environment',
+'Environment :: Web Environment :: Buffet',
+'Environment :: Web Environment :: Mozilla',
+'Environment :: Web Environment :: ToscaWidgets',
+'Environment :: Win32 (MS Windows)',
+'Environment :: X11 Applications',
+'Environment :: X11 Applications :: Gnome',
+'Environment :: X11 Applications :: GTK',
+'Environment :: X11 Applications :: KDE',
+'Environment :: X11 Applications :: Qt',
+'Framework :: BFG',
+'Framework :: Buildout',
+'Framework :: Chandler',
+'Framework :: CubicWeb',
+'Framework :: Django',
+'Framework :: IDLE',
+'Framework :: Paste',
+'Framework :: Plone',
+'Framework :: Pylons',
+'Framework :: Setuptools Plugin',
+'Framework :: Trac',
+'Framework :: TurboGears',
+'Framework :: TurboGears :: Applications',
+'Framework :: TurboGears :: Widgets',
+'Framework :: Twisted',
+'Framework :: ZODB',
+'Framework :: Zope2',
+'Framework :: Zope3',
+'Intended Audience :: Customer Service',
+'Intended Audience :: Developers',
+'Intended Audience :: Education',
+'Intended Audience :: End Users/Desktop',
+'Intended Audience :: Financial and Insurance Industry',
+'Intended Audience :: Healthcare Industry',
+'Intended Audience :: Information Technology',
+'Intended Audience :: Legal Industry',
+'Intended Audience :: Manufacturing',
+'Intended Audience :: Other Audience',
+'Intended Audience :: Religion',
+'Intended Audience :: Science/Research',
+'Intended Audience :: System Administrators',
+'Intended Audience :: Telecommunications Industry',
+'License :: Aladdin Free Public License (AFPL)',
+'License :: DFSG approved',
+'License :: Eiffel Forum License (EFL)',
+'License :: Free For Educational Use',
+'License :: Free For Home Use',
+'License :: Free for non-commercial use',
+'License :: Freely Distributable',
+'License :: Free To Use But Restricted',
+'License :: Freeware',
+'License :: Netscape Public License (NPL)',
+'License :: Nokia Open Source License (NOKOS)',
+'License :: OSI Approved',
+'License :: OSI Approved :: Academic Free License (AFL)',
+'License :: OSI Approved :: Apache Software License',
+'License :: OSI Approved :: Apple Public Source License',
+'License :: OSI Approved :: Artistic License',
+'License :: OSI Approved :: Attribution Assurance License',
+'License :: OSI Approved :: BSD License',
+'License :: OSI Approved :: Common Public License',
+'License :: OSI Approved :: Eiffel Forum License',
+'License :: OSI Approved :: European Union Public Licence 1.0 (EUPL 1.0)',
+'License :: OSI Approved :: European Union Public Licence 1.1 (EUPL 1.1)',
+'License :: OSI Approved :: GNU Affero General Public License v3',
+'License :: OSI Approved :: GNU Free Documentation License (FDL)',
+'License :: OSI Approved :: GNU General Public License (GPL)',
+'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
+'License :: OSI Approved :: IBM Public License',
+'License :: OSI Approved :: Intel Open Source License',
+'License :: OSI Approved :: ISC License (ISCL)',
+'License :: OSI Approved :: Jabber Open Source License',
+'License :: OSI Approved :: MIT License',
+'License :: OSI Approved :: MITRE Collaborative Virtual Workspace License (CVW)',
+'License :: OSI Approved :: Motosoto License',
+'License :: OSI Approved :: Mozilla Public License 1.0 (MPL)',
+'License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)',
+'License :: OSI Approved :: Nethack General Public License',
+'License :: OSI Approved :: Nokia Open Source License',
+'License :: OSI Approved :: Open Group Test Suite License',
+'License :: OSI Approved :: Python License (CNRI Python License)',
+'License :: OSI Approved :: Python Software Foundation License',
+'License :: OSI Approved :: Qt Public License (QPL)',
+'License :: OSI Approved :: Ricoh Source Code Public License',
+'License :: OSI Approved :: Sleepycat License',
+'License :: OSI Approved :: Sun Industry Standards Source License (SISSL)',
+'License :: OSI Approved :: Sun Public License',
+'License :: OSI Approved :: University of Illinois/NCSA Open Source License',
+'License :: OSI Approved :: Vovida Software License 1.0',
+'License :: OSI Approved :: W3C License',
+'License :: OSI Approved :: X.Net License',
+'License :: OSI Approved :: zlib/libpng License',
+'License :: OSI Approved :: Zope Public License',
+'License :: Other/Proprietary License',
+'License :: Public Domain',
+'License :: Repoze Public License',
+'Natural Language :: Afrikaans',
+'Natural Language :: Arabic',
+'Natural Language :: Bengali',
+'Natural Language :: Bosnian',
+'Natural Language :: Bulgarian',
+'Natural Language :: Catalan',
+'Natural Language :: Chinese (Simplified)',
+'Natural Language :: Chinese (Traditional)',
+'Natural Language :: Croatian',
+'Natural Language :: Czech',
+'Natural Language :: Danish',
+'Natural Language :: Dutch',
+'Natural Language :: English',
+'Natural Language :: Esperanto',
+'Natural Language :: Finnish',
+'Natural Language :: French',
+'Natural Language :: German',
+'Natural Language :: Greek',
+'Natural Language :: Hebrew',
+'Natural Language :: Hindi',
+'Natural Language :: Hungarian',
+'Natural Language :: Icelandic',
+'Natural Language :: Indonesian',
+'Natural Language :: Italian',
+'Natural Language :: Japanese',
+'Natural Language :: Javanese',
+'Natural Language :: Korean',
+'Natural Language :: Latin',
+'Natural Language :: Latvian',
+'Natural Language :: Macedonian',
+'Natural Language :: Malay',
+'Natural Language :: Marathi',
+'Natural Language :: Norwegian',
+'Natural Language :: Panjabi',
+'Natural Language :: Persian',
+'Natural Language :: Polish',
+'Natural Language :: Portuguese',
+'Natural Language :: Portuguese (Brazilian)',
+'Natural Language :: Romanian',
+'Natural Language :: Russian',
+'Natural Language :: Serbian',
+'Natural Language :: Slovak',
+'Natural Language :: Slovenian',
+'Natural Language :: Spanish',
+'Natural Language :: Swedish',
+'Natural Language :: Tamil',
+'Natural Language :: Telugu',
+'Natural Language :: Thai',
+'Natural Language :: Turkish',
+'Natural Language :: Ukranian',
+'Natural Language :: Urdu',
+'Natural Language :: Vietnamese',
+'Operating System :: BeOS',
+'Operating System :: MacOS',
+'Operating System :: MacOS :: MacOS 9',
+'Operating System :: MacOS :: MacOS X',
+'Operating System :: Microsoft',
+'Operating System :: Microsoft :: MS-DOS',
+'Operating System :: Microsoft :: Windows',
+'Operating System :: Microsoft :: Windows :: Windows 3.1 or Earlier',
+'Operating System :: Microsoft :: Windows :: Windows 95/98/2000',
+'Operating System :: Microsoft :: Windows :: Windows CE',
+'Operating System :: Microsoft :: Windows :: Windows NT/2000',
+'Operating System :: OS/2',
+'Operating System :: OS Independent',
+'Operating System :: Other OS',
+'Operating System :: PalmOS',
+'Operating System :: PDA Systems',
+'Operating System :: POSIX',
+'Operating System :: POSIX :: AIX',
+'Operating System :: POSIX :: BSD',
+'Operating System :: POSIX :: BSD :: BSD/OS',
+'Operating System :: POSIX :: BSD :: FreeBSD',
+'Operating System :: POSIX :: BSD :: NetBSD',
+'Operating System :: POSIX :: BSD :: OpenBSD',
+'Operating System :: POSIX :: GNU Hurd',
+'Operating System :: POSIX :: HP-UX',
+'Operating System :: POSIX :: IRIX',
+'Operating System :: POSIX :: Linux',
+'Operating System :: POSIX :: Other',
+'Operating System :: POSIX :: SCO',
+'Operating System :: POSIX :: SunOS/Solaris',
+'Operating System :: Unix',
+'Programming Language :: Ada',
+'Programming Language :: APL',
+'Programming Language :: ASP',
+'Programming Language :: Assembly',
+'Programming Language :: Awk',
+'Programming Language :: Basic',
+'Programming Language :: C',
+'Programming Language :: C#',
+'Programming Language :: C++',
+'Programming Language :: Cold Fusion',
+'Programming Language :: Cython',
+'Programming Language :: Delphi/Kylix',
+'Programming Language :: Dylan',
+'Programming Language :: Eiffel',
+'Programming Language :: Emacs-Lisp',
+'Programming Language :: Erlang',
+'Programming Language :: Euler',
+'Programming Language :: Euphoria',
+'Programming Language :: Forth',
+'Programming Language :: Fortran',
+'Programming Language :: Haskell',
+'Programming Language :: Java',
+'Programming Language :: JavaScript',
+'Programming Language :: Lisp',
+'Programming Language :: Logo',
+'Programming Language :: ML',
+'Programming Language :: Modula',
+'Programming Language :: Objective C',
+'Programming Language :: Object Pascal',
+'Programming Language :: OCaml',
+'Programming Language :: Other',
+'Programming Language :: Other Scripting Engines',
+'Programming Language :: Pascal',
+'Programming Language :: Perl',
+'Programming Language :: PHP',
+'Programming Language :: Pike',
+'Programming Language :: Pliant',
+'Programming Language :: PL/SQL',
+'Programming Language :: PROGRESS',
+'Programming Language :: Prolog',
+'Programming Language :: Python',
+'Programming Language :: Python :: 2',
+'Programming Language :: Python :: 2.3',
+'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.0',
+'Programming Language :: Python :: 3.1',
+'Programming Language :: Python :: 3.2',
+'Programming Language :: REBOL',
+'Programming Language :: Rexx',
+'Programming Language :: Ruby',
+'Programming Language :: Scheme',
+'Programming Language :: Simula',
+'Programming Language :: Smalltalk',
+'Programming Language :: SQL',
+'Programming Language :: Tcl',
+'Programming Language :: Unix Shell',
+'Programming Language :: Visual Basic',
+'Programming Language :: XBasic',
+'Programming Language :: YACC',
+'Programming Language :: Zope',
+'Topic :: Adaptive Technologies',
+'Topic :: Artistic Software',
+'Topic :: Communications',
+'Topic :: Communications :: BBS',
+'Topic :: Communications :: Chat',
+'Topic :: Communications :: Chat :: AOL Instant Messenger',
+'Topic :: Communications :: Chat :: ICQ',
+'Topic :: Communications :: Chat :: Internet Relay Chat',
+'Topic :: Communications :: Chat :: Unix Talk',
+'Topic :: Communications :: Conferencing',
+'Topic :: Communications :: Email',
+'Topic :: Communications :: Email :: Address Book',
+'Topic :: Communications :: Email :: Email Clients (MUA)',
+'Topic :: Communications :: Email :: Filters',
+'Topic :: Communications :: Email :: Mailing List Servers',
+'Topic :: Communications :: Email :: Mail Transport Agents',
+'Topic :: Communications :: Email :: Post-Office',
+'Topic :: Communications :: Email :: Post-Office :: IMAP',
+'Topic :: Communications :: Email :: Post-Office :: POP3',
+'Topic :: Communications :: Fax',
+'Topic :: Communications :: FIDO',
+'Topic :: Communications :: File Sharing',
+'Topic :: Communications :: File Sharing :: Gnutella',
+'Topic :: Communications :: File Sharing :: Napster',
+'Topic :: Communications :: Ham Radio',
+'Topic :: Communications :: Internet Phone',
+'Topic :: Communications :: Telephony',
+'Topic :: Communications :: Usenet News',
+'Topic :: Database',
+'Topic :: Database :: Database Engines/Servers',
+'Topic :: Database :: Front-Ends',
+'Topic :: Desktop Environment',
+'Topic :: Desktop Environment :: File Managers',
+'Topic :: Desktop Environment :: Gnome',
+'Topic :: Desktop Environment :: GNUstep',
+'Topic :: Desktop Environment :: K Desktop Environment (KDE)',
+'Topic :: Desktop Environment :: K Desktop Environment (KDE) :: Themes',
+'Topic :: Desktop Environment :: PicoGUI',
+'Topic :: Desktop Environment :: PicoGUI :: Applications',
+'Topic :: Desktop Environment :: PicoGUI :: Themes',
+'Topic :: Desktop Environment :: Screen Savers',
+'Topic :: Desktop Environment :: Window Managers',
+'Topic :: Desktop Environment :: Window Managers :: Afterstep',
+'Topic :: Desktop Environment :: Window Managers :: Afterstep :: Themes',
+'Topic :: Desktop Environment :: Window Managers :: Applets',
+'Topic :: Desktop Environment :: Window Managers :: Blackbox',
+'Topic :: Desktop Environment :: Window Managers :: Blackbox :: Themes',
+'Topic :: Desktop Environment :: Window Managers :: CTWM',
+'Topic :: Desktop Environment :: Window Managers :: CTWM :: Themes',
+'Topic :: Desktop Environment :: Window Managers :: Enlightenment',
+'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Epplets',
+'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR15',
+'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR16',
+'Topic :: Desktop Environment :: Window Managers :: Enlightenment :: Themes DR17',
+'Topic :: Desktop Environment :: Window Managers :: Fluxbox',
+'Topic :: Desktop Environment :: Window Managers :: Fluxbox :: Themes',
+'Topic :: Desktop Environment :: Window Managers :: FVWM',
+'Topic :: Desktop Environment :: Window Managers :: FVWM :: Themes',
+'Topic :: Desktop Environment :: Window Managers :: IceWM',
+'Topic :: Desktop Environment :: Window Managers :: IceWM :: Themes',
+'Topic :: Desktop Environment :: Window Managers :: MetaCity',
+'Topic :: Desktop Environment :: Window Managers :: MetaCity :: Themes',
+'Topic :: Desktop Environment :: Window Managers :: Oroborus',
+'Topic :: Desktop Environment :: Window Managers :: Oroborus :: Themes',
+'Topic :: Desktop Environment :: Window Managers :: Sawfish',
+'Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes 0.30',
+'Topic :: Desktop Environment :: Window Managers :: Sawfish :: Themes pre-0.30',
+'Topic :: Desktop Environment :: Window Managers :: Waimea',
+'Topic :: Desktop Environment :: Window Managers :: Waimea :: Themes',
+'Topic :: Desktop Environment :: Window Managers :: Window Maker',
+'Topic :: Desktop Environment :: Window Managers :: Window Maker :: Applets',
+'Topic :: Desktop Environment :: Window Managers :: Window Maker :: Themes',
+'Topic :: Desktop Environment :: Window Managers :: XFCE',
+'Topic :: Desktop Environment :: Window Managers :: XFCE :: Themes',
+'Topic :: Documentation',
+'Topic :: Education',
+'Topic :: Education :: Computer Aided Instruction (CAI)',
+'Topic :: Education :: Testing',
+'Topic :: Games/Entertainment',
+'Topic :: Games/Entertainment :: Arcade',
+'Topic :: Games/Entertainment :: Board Games',
+'Topic :: Games/Entertainment :: First Person Shooters',
+'Topic :: Games/Entertainment :: Fortune Cookies',
+'Topic :: Games/Entertainment :: Multi-User Dungeons (MUD)',
+'Topic :: Games/Entertainment :: Puzzle Games',
+'Topic :: Games/Entertainment :: Real Time Strategy',
+'Topic :: Games/Entertainment :: Role-Playing',
+'Topic :: Games/Entertainment :: Side-Scrolling/Arcade Games',
+'Topic :: Games/Entertainment :: Simulation',
+'Topic :: Games/Entertainment :: Turn Based Strategy',
+'Topic :: Home Automation',
+'Topic :: Internet',
+'Topic :: Internet :: File Transfer Protocol (FTP)',
+'Topic :: Internet :: Finger',
+'Topic :: Internet :: Log Analysis',
+'Topic :: Internet :: Name Service (DNS)',
+'Topic :: Internet :: Proxy Servers',
+'Topic :: Internet :: WAP',
+'Topic :: Internet :: WWW/HTTP',
+'Topic :: Internet :: WWW/HTTP :: Browsers',
+'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries',
+'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Message Boards',
+'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: News/Diary',
+'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Page Counters',
+'Topic :: Internet :: WWW/HTTP :: HTTP Servers',
+'Topic :: Internet :: WWW/HTTP :: Indexing/Search',
+'Topic :: Internet :: WWW/HTTP :: Site Management',
+'Topic :: Internet :: WWW/HTTP :: Site Management :: Link Checking',
+'Topic :: Internet :: WWW/HTTP :: WSGI',
+'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+'Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware',
+'Topic :: Internet :: WWW/HTTP :: WSGI :: Server',
+'Topic :: Internet :: Z39.50',
+'Topic :: Multimedia',
+'Topic :: Multimedia :: Graphics',
+'Topic :: Multimedia :: Graphics :: 3D Modeling',
+'Topic :: Multimedia :: Graphics :: 3D Rendering',
+'Topic :: Multimedia :: Graphics :: Capture',
+'Topic :: Multimedia :: Graphics :: Capture :: Digital Camera',
+'Topic :: Multimedia :: Graphics :: Capture :: Scanners',
+'Topic :: Multimedia :: Graphics :: Capture :: Screen Capture',
+'Topic :: Multimedia :: Graphics :: Editors',
+'Topic :: Multimedia :: Graphics :: Editors :: Raster-Based',
+'Topic :: Multimedia :: Graphics :: Editors :: Vector-Based',
+'Topic :: Multimedia :: Graphics :: Graphics Conversion',
+'Topic :: Multimedia :: Graphics :: Presentation',
+'Topic :: Multimedia :: Graphics :: Viewers',
+'Topic :: Multimedia :: Sound/Audio',
+'Topic :: Multimedia :: Sound/Audio :: Analysis',
+'Topic :: Multimedia :: Sound/Audio :: Capture/Recording',
+'Topic :: Multimedia :: Sound/Audio :: CD Audio',
+'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Playing',
+'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Ripping',
+'Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Writing',
+'Topic :: Multimedia :: Sound/Audio :: Conversion',
+'Topic :: Multimedia :: Sound/Audio :: Editors',
+'Topic :: Multimedia :: Sound/Audio :: MIDI',
+'Topic :: Multimedia :: Sound/Audio :: Mixers',
+'Topic :: Multimedia :: Sound/Audio :: Players',
+'Topic :: Multimedia :: Sound/Audio :: Players :: MP3',
+'Topic :: Multimedia :: Sound/Audio :: Sound Synthesis',
+'Topic :: Multimedia :: Sound/Audio :: Speech',
+'Topic :: Multimedia :: Video',
+'Topic :: Multimedia :: Video :: Capture',
+'Topic :: Multimedia :: Video :: Conversion',
+'Topic :: Multimedia :: Video :: Display',
+'Topic :: Multimedia :: Video :: Non-Linear Editor',
+'Topic :: Office/Business',
+'Topic :: Office/Business :: Financial',
+'Topic :: Office/Business :: Financial :: Accounting',
+'Topic :: Office/Business :: Financial :: Investment',
+'Topic :: Office/Business :: Financial :: Point-Of-Sale',
+'Topic :: Office/Business :: Financial :: Spreadsheet',
+'Topic :: Office/Business :: Groupware',
+'Topic :: Office/Business :: News/Diary',
+'Topic :: Office/Business :: Office Suites',
+'Topic :: Office/Business :: Scheduling',
+'Topic :: Other/Nonlisted Topic',
+'Topic :: Printing',
+'Topic :: Religion',
+'Topic :: Scientific/Engineering',
+'Topic :: Scientific/Engineering :: Artificial Intelligence',
+'Topic :: Scientific/Engineering :: Astronomy',
+'Topic :: Scientific/Engineering :: Atmospheric Science',
+'Topic :: Scientific/Engineering :: Bio-Informatics',
+'Topic :: Scientific/Engineering :: Chemistry',
+'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)',
+'Topic :: Scientific/Engineering :: GIS',
+'Topic :: Scientific/Engineering :: Human Machine Interfaces',
+'Topic :: Scientific/Engineering :: Image Recognition',
+'Topic :: Scientific/Engineering :: Information Analysis',
+'Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator',
+'Topic :: Scientific/Engineering :: Mathematics',
+'Topic :: Scientific/Engineering :: Medical Science Apps.',
+'Topic :: Scientific/Engineering :: Physics',
+'Topic :: Scientific/Engineering :: Visualization',
+'Topic :: Security',
+'Topic :: Security :: Cryptography',
+'Topic :: Sociology',
+'Topic :: Sociology :: Genealogy',
+'Topic :: Sociology :: History',
+'Topic :: Software Development',
+'Topic :: Software Development :: Assemblers',
+'Topic :: Software Development :: Bug Tracking',
+'Topic :: Software Development :: Build Tools',
+'Topic :: Software Development :: Code Generators',
+'Topic :: Software Development :: Compilers',
+'Topic :: Software Development :: Debuggers',
+'Topic :: Software Development :: Disassemblers',
+'Topic :: Software Development :: Documentation',
+'Topic :: Software Development :: Embedded Systems',
+'Topic :: Software Development :: Internationalization',
+'Topic :: Software Development :: Interpreters',
+'Topic :: Software Development :: Libraries',
+'Topic :: Software Development :: Libraries :: Application Frameworks',
+'Topic :: Software Development :: Libraries :: Java Libraries',
+'Topic :: Software Development :: Libraries :: Perl Modules',
+'Topic :: Software Development :: Libraries :: PHP Classes',
+'Topic :: Software Development :: Libraries :: Pike Modules',
+'Topic :: Software Development :: Libraries :: pygame',
+'Topic :: Software Development :: Libraries :: Python Modules',
+'Topic :: Software Development :: Libraries :: Ruby Modules',
+'Topic :: Software Development :: Libraries :: Tcl Extensions',
+'Topic :: Software Development :: Localization',
+'Topic :: Software Development :: Object Brokering',
+'Topic :: Software Development :: Object Brokering :: CORBA',
+'Topic :: Software Development :: Pre-processors',
+'Topic :: Software Development :: Quality Assurance',
+'Topic :: Software Development :: Testing',
+'Topic :: Software Development :: Testing :: Traffic Generation',
+'Topic :: Software Development :: User Interfaces',
+'Topic :: Software Development :: Version Control',
+'Topic :: Software Development :: Version Control :: CVS',
+'Topic :: Software Development :: Version Control :: RCS',
+'Topic :: Software Development :: Version Control :: SCCS',
+'Topic :: Software Development :: Widget Sets',
+'Topic :: System',
+'Topic :: System :: Archiving',
+'Topic :: System :: Archiving :: Backup',
+'Topic :: System :: Archiving :: Compression',
+'Topic :: System :: Archiving :: Mirroring',
+'Topic :: System :: Archiving :: Packaging',
+'Topic :: System :: Benchmark',
+'Topic :: System :: Boot',
+'Topic :: System :: Boot :: Init',
+'Topic :: System :: Clustering',
+'Topic :: System :: Console Fonts',
+'Topic :: System :: Distributed Computing',
+'Topic :: System :: Emulators',
+'Topic :: System :: Filesystems',
+'Topic :: System :: Hardware',
+'Topic :: System :: Hardware :: Hardware Drivers',
+'Topic :: System :: Hardware :: Mainframes',
+'Topic :: System :: Hardware :: Symmetric Multi-processing',
+'Topic :: System :: Installation/Setup',
+'Topic :: System :: Logging',
+'Topic :: System :: Monitoring',
+'Topic :: System :: Networking',
+'Topic :: System :: Networking :: Firewalls',
+'Topic :: System :: Networking :: Monitoring',
+'Topic :: System :: Networking :: Monitoring :: Hardware Watchdog',
+'Topic :: System :: Networking :: Time Synchronization',
+'Topic :: System :: Operating System',
+'Topic :: System :: Operating System Kernels',
+'Topic :: System :: Operating System Kernels :: BSD',
+'Topic :: System :: Operating System Kernels :: GNU Hurd',
+'Topic :: System :: Operating System Kernels :: Linux',
+'Topic :: System :: Power (UPS)',
+'Topic :: System :: Recovery Tools',
+'Topic :: System :: Shells',
+'Topic :: System :: Software Distribution',
+'Topic :: System :: Systems Administration',
+'Topic :: System :: Systems Administration :: Authentication/Directory',
+'Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP',
+'Topic :: System :: Systems Administration :: Authentication/Directory :: NIS',
+'Topic :: System :: System Shells',
+'Topic :: Terminals',
+'Topic :: Terminals :: Serial',
+'Topic :: Terminals :: Telnet',
+'Topic :: Terminals :: Terminal Emulators/X Terminals',
+'Topic :: Text Editors',
+'Topic :: Text Editors :: Documentation',
+'Topic :: Text Editors :: Emacs',
+'Topic :: Text Editors :: Integrated Development Environments (IDE)',
+'Topic :: Text Editors :: Text Processing',
+'Topic :: Text Editors :: Word Processors',
+'Topic :: Text Processing',
+'Topic :: Text Processing :: Filters',
+'Topic :: Text Processing :: Fonts',
+'Topic :: Text Processing :: General',
+'Topic :: Text Processing :: Indexing',
+'Topic :: Text Processing :: Linguistic',
+'Topic :: Text Processing :: Markup',
+'Topic :: Text Processing :: Markup :: HTML',
+'Topic :: Text Processing :: Markup :: LaTeX',
+'Topic :: Text Processing :: Markup :: SGML',
+'Topic :: Text Processing :: Markup :: VRML',
+'Topic :: Text Processing :: Markup :: XML',
+'Topic :: Utilities',
]
diff --git a/distutils2/command/__init__.py b/distutils2/command/__init__.py
--- a/distutils2/command/__init__.py
+++ b/distutils2/command/__init__.py
@@ -1,8 +1,6 @@
-"""distutils.command
+"""Subpackage containing all standard commands."""
-Package containing implementation of all the standard Distutils
-commands."""
-from distutils2.errors import DistutilsModuleError
+from distutils2.errors import PackagingModuleError
from distutils2.util import resolve_name
__all__ = ['get_command_names', 'set_command', 'get_command_class',
@@ -50,9 +48,9 @@
"""Return the registered command"""
try:
cls = _COMMANDS[name]
- if isinstance(cls, str):
+ if isinstance(cls, basestring):
cls = resolve_name(cls)
_COMMANDS[name] = cls
return cls
except KeyError:
- raise DistutilsModuleError("Invalid command %s" % name)
+ raise PackagingModuleError("Invalid command %s" % name)
diff --git a/distutils2/command/bdist.py b/distutils2/command/bdist.py
--- a/distutils2/command/bdist.py
+++ b/distutils2/command/bdist.py
@@ -1,12 +1,15 @@
-"""distutils.command.bdist
+"""Create a built (binary) distribution.
-Implements the Distutils 'bdist' command (create a built [binary]
-distribution)."""
+If a --formats option was given on the command line, this command will
+call the corresponding bdist_* commands; if the option was absent, a
+bdist_* command depending on the current platform will be called.
+"""
+
import os
from distutils2 import util
from distutils2.command.cmd import Command
-from distutils2.errors import DistutilsPlatformError, DistutilsOptionError
+from distutils2.errors import PackagingPlatformError, PackagingOptionError
def show_formats():
@@ -52,8 +55,10 @@
"lists available distribution formats", show_formats),
]
- # This won't do in reality: will need to distinguish RPM-ish Linux,
- # Debian-ish Linux, Solaris, FreeBSD, ..., Windows, Mac OS.
+ # This is of course very simplistic. The various UNIX family operating
+ # systems have their specific formats, but they are out of scope for us;
+ # bdist_dumb is, well, dumb; it's more a building block for other
+ # distutils2 tools than a real end-user binary format.
default_format = {'posix': 'gztar',
'nt': 'zip',
'os2': 'zip'}
@@ -79,7 +84,7 @@
self.plat_name = None
self.formats = None
self.dist_dir = None
- self.skip_build = 0
+ self.skip_build = False
self.group = None
self.owner = None
@@ -104,9 +109,8 @@
try:
self.formats = [self.default_format[os.name]]
except KeyError:
- raise DistutilsPlatformError, \
- "don't know how to create built distributions " + \
- "on platform %s" % os.name
+ raise PackagingPlatformError("don't know how to create built distributions " + \
+ "on platform %s" % os.name)
if self.dist_dir is None:
self.dist_dir = "dist"
@@ -118,12 +122,13 @@
try:
commands.append(self.format_command[format][0])
except KeyError:
- raise DistutilsOptionError, "invalid format '%s'" % format
+ raise PackagingOptionError("invalid format '%s'" % format)
# Reinitialize and run each command.
for i in range(len(self.formats)):
cmd_name = commands[i]
sub_cmd = self.get_reinitialized_command(cmd_name)
+ sub_cmd.format = self.formats[i]
# passing the owner and group names for tar archiving
if cmd_name == 'bdist_dumb':
@@ -133,5 +138,5 @@
# If we're going to need to run this command again, tell it to
# keep its temporary files around so subsequent runs go faster.
if cmd_name in commands[i+1:]:
- sub_cmd.keep_temp = 1
+ sub_cmd.keep_temp = True
self.run_command(cmd_name)
diff --git a/distutils2/command/bdist_dumb.py b/distutils2/command/bdist_dumb.py
--- a/distutils2/command/bdist_dumb.py
+++ b/distutils2/command/bdist_dumb.py
@@ -1,22 +1,19 @@
-"""distutils.command.bdist_dumb
+"""Create a "dumb" built distribution.
-Implements the Distutils 'bdist_dumb' command (create a "dumb" built
-distribution -- i.e., just an archive to be unpacked under $prefix or
-$exec_prefix)."""
-
+A dumb distribution is just an archive meant to be unpacked under
+sys.prefix or sys.exec_prefix.
+"""
import os
+
from shutil import rmtree
-try:
- from sysconfig import get_python_version
-except ImportError:
- from distutils2._backport.sysconfig import get_python_version
+from sysconfig import get_python_version
from distutils2.util import get_platform
from distutils2.command.cmd import Command
-from distutils2.errors import DistutilsPlatformError
+from distutils2.errors import PackagingPlatformError
from distutils2 import logger
-class bdist_dumb (Command):
+class bdist_dumb(Command):
description = 'create a "dumb" built distribution'
@@ -52,14 +49,14 @@
'os2': 'zip' }
- def initialize_options (self):
+ def initialize_options(self):
self.bdist_dir = None
self.plat_name = None
self.format = None
- self.keep_temp = 0
+ self.keep_temp = False
self.dist_dir = None
- self.skip_build = 0
- self.relative = 0
+ self.skip_build = False
+ self.relative = False
self.owner = None
self.group = None
@@ -72,9 +69,8 @@
try:
self.format = self.default_format[os.name]
except KeyError:
- raise DistutilsPlatformError, \
- ("don't know how to create dumb built distributions " +
- "on platform %s") % os.name
+ raise PackagingPlatformError(("don't know how to create dumb built distributions " +
+ "on platform %s") % os.name)
self.set_undefined_options('bdist', 'dist_dir', 'plat_name')
@@ -82,10 +78,11 @@
if not self.skip_build:
self.run_command('build')
- install = self.get_reinitialized_command('install_dist', reinit_subcommands=1)
+ install = self.get_reinitialized_command('install_dist',
+ reinit_subcommands=True)
install.root = self.bdist_dir
install.skip_build = self.skip_build
- install.warn_dir = 0
+ install.warn_dir = False
logger.info("installing to %s", self.bdist_dir)
self.run_command('install_dist')
@@ -106,7 +103,7 @@
else:
if (self.distribution.has_ext_modules() and
(install.install_base != install.install_platbase)):
- raise DistutilsPlatformError(
+ raise PackagingPlatformError(
"can't make a dumb built distribution where base and "
"platbase are different (%r, %r)" %
(install.install_base, install.install_platbase))
diff --git a/distutils2/command/bdist_msi.py b/distutils2/command/bdist_msi.py
--- a/distutils2/command/bdist_msi.py
+++ b/distutils2/command/bdist_msi.py
@@ -1,25 +1,33 @@
-# -*- coding: iso-8859-1 -*-
-# Copyright (C) 2005, 2006 Martin von L�wis
+"""Create a Microsoft Installer (.msi) binary distribution."""
+
+# Copyright (C) 2005, 2006 Martin von Löwis
# Licensed to PSF under a Contributor Agreement.
-# The bdist_wininst command proper
-# based on bdist_wininst
-"""
-Implements the bdist_msi command.
-"""
-import sys, os
+
+import sys
+import os
+import msilib
+
+
from sysconfig import get_python_version
-
-from distutils2.core import Command
-from distutils2.version import StrictVersion
-from distutils2.errors import DistutilsOptionError
-from distutils2 import log
+from shutil import rmtree
+from distutils2.command.cmd import Command
+from distutils2.version import NormalizedVersion
+from distutils2.errors import PackagingOptionError
+from distutils2 import logger as log
from distutils2.util import get_platform
-from distutils2._backport.shutil import rmtree
-
-import msilib
from msilib import schema, sequence, text
from msilib import Directory, Feature, Dialog, add_data
+class MSIVersion(NormalizedVersion):
+ """
+ MSI ProductVersion must be strictly numeric.
+ MSIVersion disallows prerelease and postrelease versions.
+ """
+ def __init__(self, *args, **kwargs):
+ super(MSIVersion, self).__init__(*args, **kwargs)
+ if not self.is_final:
+ raise ValueError("ProductVersion must be strictly numeric")
+
class PyDialog(Dialog):
"""Dialog class with a fixed layout: controls at the top, then a ruler,
then a list of buttons: back, next, cancel. Optionally a bitmap at the
@@ -81,7 +89,7 @@
Return the button, so that events can be associated"""
return self.pushbutton(name, int(self.w*xpos - 28), self.h-27, 56, 17, 3, title, next)
-class bdist_msi (Command):
+class bdist_msi(Command):
description = "create a Microsoft Installer (.msi) binary distribution"
@@ -123,20 +131,20 @@
'3.5', '3.6', '3.7', '3.8', '3.9']
other_version = 'X'
- def initialize_options (self):
+ def initialize_options(self):
self.bdist_dir = None
self.plat_name = None
- self.keep_temp = 0
- self.no_target_compile = 0
- self.no_target_optimize = 0
+ self.keep_temp = False
+ self.no_target_compile = False
+ self.no_target_optimize = False
self.target_version = None
self.dist_dir = None
- self.skip_build = 0
+ self.skip_build = False
self.install_script = None
self.pre_install_script = None
self.versions = None
- def finalize_options (self):
+ def finalize_options(self):
if self.bdist_dir is None:
bdist_base = self.get_finalized_command('bdist').bdist_base
self.bdist_dir = os.path.join(bdist_base, 'msi')
@@ -147,41 +155,39 @@
self.versions = [self.target_version]
if not self.skip_build and self.distribution.has_ext_modules()\
and self.target_version != short_version:
- raise DistutilsOptionError, \
- "target version can only be %s, or the '--skip-build'" \
- " option must be specified" % (short_version,)
+ raise PackagingOptionError("target version can only be %s, or the '--skip-build'" \
+ " option must be specified" % (short_version,))
else:
self.versions = list(self.all_versions)
self.set_undefined_options('bdist', 'dist_dir', 'plat_name')
if self.pre_install_script:
- raise DistutilsOptionError, "the pre-install-script feature is not yet implemented"
+ raise PackagingOptionError("the pre-install-script feature is not yet implemented")
if self.install_script:
for script in self.distribution.scripts:
if self.install_script == os.path.basename(script):
break
else:
- raise DistutilsOptionError, \
- "install_script '%s' not found in scripts" % \
- self.install_script
+ raise PackagingOptionError("install_script '%s' not found in scripts" % \
+ self.install_script)
self.install_script_key = None
- # finalize_options()
- def run (self):
+ def run(self):
if not self.skip_build:
self.run_command('build')
- install = self.get_reinitialized_command('install_dist', reinit_subcommands=1)
+ install = self.get_reinitialized_command('install_dist',
+ reinit_subcommands=True)
install.prefix = self.bdist_dir
install.skip_build = self.skip_build
- install.warn_dir = 0
+ install.warn_dir = False
install_lib = self.get_reinitialized_command('install_lib')
# we do not want to include pyc or pyo files
- install_lib.compile = 0
+ install_lib.compile = False
install_lib.optimize = 0
if self.distribution.has_ext_modules():
@@ -223,10 +229,7 @@
author = metadata.maintainer
if not author:
author = "UNKNOWN"
- version = metadata.get_version()
- # ProductVersion must be strictly numeric
- # XXX need to deal with prerelease versions
- sversion = "%d.%d.%d" % StrictVersion(version).version
+ version = MSIVersion(metadata.get_version())
# Prefix ProductName with Python x.y, so that
# it sorts together with the other Python packages
# in Add-Remove-Programs (APR)
@@ -237,7 +240,7 @@
product_name = "Python %s" % (fullname)
self.db = msilib.init_database(installer_name, schema,
product_name, msilib.gen_uuid(),
- sversion, author)
+ str(version), author)
msilib.add_tables(self.db, sequence)
props = [('DistVersion', version)]
email = metadata.author_email or metadata.maintainer_email
@@ -307,7 +310,7 @@
key = seen[afile] = dir.add_file(file)
if file==self.install_script:
if self.install_script_key:
- raise DistutilsOptionError(
+ raise PackagingOptionError(
"Multiple files with name %s" % file)
self.install_script_key = '[#%s]' % key
else:
@@ -387,27 +390,27 @@
# entries for each version as the above code does
if self.pre_install_script:
scriptfn = os.path.join(self.bdist_dir, "preinstall.bat")
- f = open(scriptfn, "w")
- # The batch file will be executed with [PYTHON], so that %1
- # is the path to the Python interpreter; %0 will be the path
- # of the batch file.
- # rem ="""
- # %1 %0
- # exit
- # """
- # <actual script>
- f.write('rem ="""\n%1 %0\nexit\n"""\n')
- f.write(open(self.pre_install_script).read())
- f.close()
+ with open(scriptfn, "w") as f:
+ # The batch file will be executed with [PYTHON], so that %1
+ # is the path to the Python interpreter; %0 will be the path
+ # of the batch file.
+ # rem ="""
+ # %1 %0
+ # exit
+ # """
+ # <actual script>
+ f.write('rem ="""\n%1 %0\nexit\n"""\n')
+ with open(self.pre_install_script) as fp:
+ f.write(fp.read())
add_data(self.db, "Binary",
- [("PreInstall", msilib.Binary(scriptfn))
- ])
+ [("PreInstall", msilib.Binary(scriptfn)),
+ ])
add_data(self.db, "CustomAction",
- [("PreInstall", 2, "PreInstall", None)
- ])
+ [("PreInstall", 2, "PreInstall", None),
+ ])
add_data(self.db, "InstallExecuteSequence",
- [("PreInstall", "NOT Installed", 450)])
-
+ [("PreInstall", "NOT Installed", 450),
+ ])
def add_ui(self):
db = self.db
diff --git a/distutils2/command/bdist_wininst.py b/distutils2/command/bdist_wininst.py
--- a/distutils2/command/bdist_wininst.py
+++ b/distutils2/command/bdist_wininst.py
@@ -1,25 +1,21 @@
-"""distutils.command.bdist_wininst
+"""Create an executable installer for Windows."""
-Implements the Distutils 'bdist_wininst' command: create a windows installer
-exe-program."""
-
+# FIXME synchronize bytes/str use with same file in distutils
import sys
import os
-import string
+
from shutil import rmtree
-try:
- from sysconfig import get_python_version
-except ImportError:
- from distutils2._backport.sysconfig import get_python_version
+from sysconfig import get_python_version
from distutils2.command.cmd import Command
-from distutils2.errors import DistutilsOptionError, DistutilsPlatformError
+from distutils2.errors import PackagingOptionError, PackagingPlatformError
from distutils2 import logger
from distutils2.util import get_platform
-class bdist_wininst (Command):
- description = "create an executable installer for MS Windows"
+class bdist_wininst(Command):
+
+ description = "create an executable installer for Windows"
user_options = [('bdist-dir=', None,
"temporary directory for creating the distribution"),
@@ -61,25 +57,23 @@
boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize',
'skip-build']
- def initialize_options (self):
+ def initialize_options(self):
self.bdist_dir = None
self.plat_name = None
- self.keep_temp = 0
- self.no_target_compile = 0
- self.no_target_optimize = 0
+ self.keep_temp = False
+ self.no_target_compile = False
+ self.no_target_optimize = False
self.target_version = None
self.dist_dir = None
self.bitmap = None
self.title = None
- self.skip_build = 0
+ self.skip_build = False
self.install_script = None
self.pre_install_script = None
self.user_access_control = None
- # initialize_options()
-
- def finalize_options (self):
+ def finalize_options(self):
if self.bdist_dir is None:
if self.skip_build and self.plat_name:
# If build is skipped and plat_name is overridden, bdist will
@@ -94,9 +88,8 @@
if not self.skip_build and self.distribution.has_ext_modules():
short_version = get_python_version()
if self.target_version and self.target_version != short_version:
- raise DistutilsOptionError, \
- "target version can only be %s, or the '--skip-build'" \
- " option must be specified" % (short_version,)
+ raise PackagingOptionError("target version can only be %s, or the '--skip-build'" \
+ " option must be specified" % (short_version,))
self.target_version = short_version
self.set_undefined_options('bdist', 'dist_dir', 'plat_name')
@@ -106,32 +99,30 @@
if self.install_script == os.path.basename(script):
break
else:
- raise DistutilsOptionError, \
- "install_script '%s' not found in scripts" % \
- self.install_script
- # finalize_options()
+ raise PackagingOptionError("install_script '%s' not found in scripts" % \
+ self.install_script)
-
- def run (self):
+ def run(self):
if (sys.platform != "win32" and
(self.distribution.has_ext_modules() or
self.distribution.has_c_libraries())):
- raise DistutilsPlatformError \
+ raise PackagingPlatformError \
("distribution contains extensions and/or C libraries; "
"must be compiled on a Windows 32 platform")
if not self.skip_build:
self.run_command('build')
- install = self.get_reinitialized_command('install', reinit_subcommands=1)
+ install = self.get_reinitialized_command('install',
+ reinit_subcommands=True)
install.root = self.bdist_dir
install.skip_build = self.skip_build
- install.warn_dir = 0
+ install.warn_dir = False
install.plat_name = self.plat_name
install_lib = self.get_reinitialized_command('install_lib')
# we do not want to include pyc or pyo files
- install_lib.compile = 0
+ install_lib.compile = False
install_lib.optimize = 0
if self.distribution.has_ext_modules():
@@ -153,7 +144,7 @@
# Use a custom scheme for the zip-file, because we have to decide
# at installation time which scheme to use.
for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'):
- value = string.upper(key)
+ value = key.upper()
if key == 'headers':
value = value + '/Include/$dist_name'
setattr(install,
@@ -196,7 +187,7 @@
else:
rmtree(self.bdist_dir)
- def get_inidata (self):
+ def get_inidata(self):
# Return data describing the installation.
lines = []
@@ -211,14 +202,14 @@
# Escape newline characters
def escape(s):
- return string.replace(s, "\n", "\\n")
+ return s.replace("\n", "\\n")
for name in ["author", "author_email", "description", "maintainer",
"maintainer_email", "name", "url", "version"]:
data = getattr(metadata, name, "")
if data:
info = info + ("\n %s: %s" % \
- (string.capitalize(name), escape(data)))
+ (name.capitalize(), escape(data)))
lines.append("%s=%s" % (name, escape(data)))
# The [setup] section contains entries controlling
@@ -241,11 +232,9 @@
build_info = "Built %s with distutils2-%s" % \
(time.ctime(time.time()), distutils2.__version__)
lines.append("build_info=%s" % build_info)
- return string.join(lines, "\n")
+ return "\n".join(lines)
- # get_inidata()
-
- def create_exe (self, arcname, fullname, bitmap=None):
+ def create_exe(self, arcname, fullname, bitmap=None):
import struct
self.mkpath(self.dist_dir)
@@ -253,52 +242,48 @@
cfgdata = self.get_inidata()
installer_name = self.get_installer_filename(fullname)
- self.announce("creating %s" % installer_name)
+ logger.info("creating %s", installer_name)
if bitmap:
- bitmapdata = open(bitmap, "rb").read()
+ with open(bitmap, "rb") as fp:
+ bitmapdata = fp.read()
bitmaplen = len(bitmapdata)
else:
bitmaplen = 0
- file = open(installer_name, "wb")
- file.write(self.get_exe_bytes())
- if bitmap:
- file.write(bitmapdata)
+ with open(installer_name, "wb") as file:
+ file.write(self.get_exe_bytes())
+ if bitmap:
+ file.write(bitmapdata)
- # Convert cfgdata from unicode to ascii, mbcs encoded
- try:
- unicode
- except NameError:
- pass
- else:
+ # Convert cfgdata from unicode to ascii, mbcs encoded
if isinstance(cfgdata, unicode):
cfgdata = cfgdata.encode("mbcs")
- # Append the pre-install script
- cfgdata = cfgdata + "\0"
- if self.pre_install_script:
- script_data = open(self.pre_install_script, "r").read()
- cfgdata = cfgdata + script_data + "\n\0"
- else:
- # empty pre-install script
+ # Append the pre-install script
cfgdata = cfgdata + "\0"
- file.write(cfgdata)
+ if self.pre_install_script:
+ with open(self.pre_install_script) as fp:
+ script_data = fp.read()
+ cfgdata = cfgdata + script_data + "\n\0"
+ else:
+ # empty pre-install script
+ cfgdata = cfgdata + "\0"
+ file.write(cfgdata)
- # The 'magic number' 0x1234567B is used to make sure that the
- # binary layout of 'cfgdata' is what the wininst.exe binary
- # expects. If the layout changes, increment that number, make
- # the corresponding changes to the wininst.exe sources, and
- # recompile them.
- header = struct.pack("<iii",
- 0x1234567B, # tag
- len(cfgdata), # length
- bitmaplen, # number of bytes in bitmap
- )
- file.write(header)
- file.write(open(arcname, "rb").read())
-
- # create_exe()
+ # The 'magic number' 0x1234567B is used to make sure that the
+ # binary layout of 'cfgdata' is what the wininst.exe binary
+ # expects. If the layout changes, increment that number, make
+ # the corresponding changes to the wininst.exe sources, and
+ # recompile them.
+ header = struct.pack("<iii",
+ 0x1234567B, # tag
+ len(cfgdata), # length
+ bitmaplen, # number of bytes in bitmap
+ )
+ file.write(header)
+ with open(arcname, "rb") as fp:
+ file.write(fp.read())
def get_installer_filename(self, fullname):
# Factored out to allow overriding in subclasses
@@ -312,9 +297,8 @@
installer_name = os.path.join(self.dist_dir,
"%s.%s.exe" % (fullname, self.plat_name))
return installer_name
- # get_installer_filename()
- def get_exe_bytes (self):
+ def get_exe_bytes(self):
from distutils2.compiler.msvccompiler import get_build_version
# If a target-version other than the current version has been
# specified, then using the MSVC version from *this* build is no good.
@@ -354,5 +338,5 @@
sfix = ''
filename = os.path.join(directory, "wininst-%.1f%s.exe" % (bv, sfix))
- return open(filename, "rb").read()
-# class bdist_wininst
+ with open(filename, "rb") as fp:
+ return fp.read()
diff --git a/distutils2/command/build.py b/distutils2/command/build.py
--- a/distutils2/command/build.py
+++ b/distutils2/command/build.py
@@ -1,13 +1,11 @@
-"""distutils.command.build
+"""Main build command, which calls the other build_* commands."""
-Implements the Distutils 'build' command.
-"""
import sys
import os
from distutils2.util import get_platform
from distutils2.command.cmd import Command
-from distutils2.errors import DistutilsOptionError
+from distutils2.errors import PackagingOptionError
from distutils2.compiler import show_compilers
@@ -67,7 +65,7 @@
self.compiler = None
self.plat_name = None
self.debug = None
- self.force = 0
+ self.force = False
self.executable = None
self.use_2to3 = False
self.convert_2to3_doctests = None
@@ -81,7 +79,7 @@
# supported via ./configure flags, if at all). Avoid misleading
# other platforms.
if os.name != 'nt':
- raise DistutilsOptionError(
+ raise PackagingOptionError(
"--plat-name only supported on Windows (try "
"using './configure --help' on your platform)")
diff --git a/distutils2/command/build_clib.py b/distutils2/command/build_clib.py
--- a/distutils2/command/build_clib.py
+++ b/distutils2/command/build_clib.py
@@ -1,10 +1,8 @@
-"""distutils.command.build_clib
+"""Build C/C++ libraries.
-Implements the Distutils 'build_clib' command, to build a C/C++ library
-that is included in the module distribution and needed by an extension
-module."""
-
-
+This command is useful to build libraries that are included in the
+distribution and needed by extension modules.
+"""
# XXX this module has *lots* of code ripped-off quite transparently from
# build_ext.py -- not surprisingly really, as the work required to build
@@ -17,7 +15,7 @@
import os
from distutils2.command.cmd import Command
-from distutils2.errors import DistutilsSetupError
+from distutils2.errors import PackagingSetupError
from distutils2.compiler import customize_compiler
from distutils2 import logger
@@ -29,7 +27,7 @@
class build_clib(Command):
- description = "build C/C++ libraries used by Python extensions"
+ description = "build C/C++ libraries used by extension modules"
user_options = [
('build-clib=', 'b',
@@ -63,7 +61,7 @@
self.define = None
self.undef = None
self.debug = None
- self.force = 0
+ self.force = False
self.compiler = None
@@ -84,7 +82,7 @@
if self.include_dirs is None:
self.include_dirs = self.distribution.include_dirs or []
- if isinstance(self.include_dirs, str):
+ if isinstance(self.include_dirs, basestring):
self.include_dirs = self.include_dirs.split(os.pathsep)
# XXX same as for build_ext -- what about 'self.define' and
@@ -105,7 +103,7 @@
self.compiler.set_include_dirs(self.include_dirs)
if self.define is not None:
# 'define' option is a list of (name,value) tuples
- for (name,value) in self.define:
+ for name, value in self.define:
self.compiler.define_macro(name, value)
if self.undef is not None:
for macro in self.undef:
@@ -121,34 +119,29 @@
This method checks that it is a list of 2-tuples, where the tuples
are (library_name, build_info_dict).
- Raise DistutilsSetupError if the structure is invalid anywhere;
+ Raise PackagingSetupError if the structure is invalid anywhere;
just returns otherwise.
"""
if not isinstance(libraries, list):
- raise DistutilsSetupError, \
- "'libraries' option must be a list of tuples"
+ raise PackagingSetupError("'libraries' option must be a list of tuples")
for lib in libraries:
if not isinstance(lib, tuple) and len(lib) != 2:
- raise DistutilsSetupError, \
- "each element of 'libraries' must a 2-tuple"
+ raise PackagingSetupError("each element of 'libraries' must a 2-tuple")
name, build_info = lib
- if not isinstance(name, str):
- raise DistutilsSetupError, \
- "first element of each tuple in 'libraries' " + \
- "must be a string (the library name)"
+ if not isinstance(name, basestring):
+ raise PackagingSetupError("first element of each tuple in 'libraries' " + \
+ "must be a string (the library name)")
if '/' in name or (os.sep != '/' and os.sep in name):
- raise DistutilsSetupError, \
- ("bad library name '%s': " +
+ raise PackagingSetupError(("bad library name '%s': " +
"may not contain directory separators") % \
- lib[0]
+ lib[0])
if not isinstance(build_info, dict):
- raise DistutilsSetupError, \
- "second element of each tuple in 'libraries' " + \
- "must be a dictionary (build info)"
+ raise PackagingSetupError("second element of each tuple in 'libraries' " + \
+ "must be a dictionary (build info)")
def get_library_names(self):
# Assume the library list is valid -- 'check_library_list()' is
@@ -157,7 +150,7 @@
return None
lib_names = []
- for (lib_name, build_info) in self.libraries:
+ for lib_name, build_info in self.libraries:
lib_names.append(lib_name)
return lib_names
@@ -165,25 +158,23 @@
def get_source_files(self):
self.check_library_list(self.libraries)
filenames = []
- for (lib_name, build_info) in self.libraries:
+ for lib_name, build_info in self.libraries:
sources = build_info.get('sources')
if sources is None or not isinstance(sources, (list, tuple)):
- raise DistutilsSetupError, \
- ("in 'libraries' option (library '%s'), "
+ raise PackagingSetupError(("in 'libraries' option (library '%s'), "
"'sources' must be present and must be "
- "a list of source filenames") % lib_name
+ "a list of source filenames") % lib_name)
filenames.extend(sources)
return filenames
def build_libraries(self, libraries):
- for (lib_name, build_info) in libraries:
+ for lib_name, build_info in libraries:
sources = build_info.get('sources')
if sources is None or not isinstance(sources, (list, tuple)):
- raise DistutilsSetupError, \
- ("in 'libraries' option (library '%s'), " +
+ raise PackagingSetupError(("in 'libraries' option (library '%s'), " +
"'sources' must be present and must be " +
- "a list of source filenames") % lib_name
+ "a list of source filenames") % lib_name)
sources = list(sources)
logger.info("building '%s' library", lib_name)
diff --git a/distutils2/command/build_ext.py b/distutils2/command/build_ext.py
--- a/distutils2/command/build_ext.py
+++ b/distutils2/command/build_ext.py
@@ -1,30 +1,26 @@
-"""distutils.command.build_ext
+"""Build extension modules."""
-Implements the Distutils 'build_ext' command, for building extension
-modules (currently limited to C extensions, should accommodate C++
-extensions ASAP)."""
+# FIXME Is this module limited to C extensions or do C++ extensions work too?
+# The docstring of this module said that C++ was not supported, but other
+# comments contradict that.
-
-import sys, os, re
-from warnings import warn
+import os
+import re
+import sys
+import logging
+import sysconfig
from distutils2.util import get_platform
from distutils2.command.cmd import Command
-from distutils2.errors import (CCompilerError, CompileError, DistutilsError,
- DistutilsPlatformError, DistutilsSetupError)
+from distutils2.errors import (CCompilerError, CompileError, PackagingError,
+ PackagingPlatformError, PackagingSetupError)
from distutils2.compiler import customize_compiler, show_compilers
from distutils2.util import newer_group
from distutils2.compiler.extension import Extension
from distutils2 import logger
-from distutils2._backport import sysconfig
-# this keeps compatibility from 2.3 to 2.5
-if sys.version < "2.6":
- USER_BASE = None
- HAS_USER_SITE = False
-else:
- from site import USER_BASE
- HAS_USER_SITE = True
+import site
+HAS_USER_SITE = True
if os.name == 'nt':
from distutils2.compiler.msvccompiler import get_build_version
@@ -38,11 +34,11 @@
class build_ext(Command):
- description = "build C/C++ extensions (compile/link to build directory)"
+ description = "build C/C++ extension modules (compile/link to build directory)"
# XXX thoughts on how to deal with complex command-line options like
# these, i.e. how to make it so fancy_getopt can suck them off the
- # command line and make it look like setup.py defined the appropriate
+ # command line and turn them into the appropriate
# lists of tuples of what-have-you.
# - each command needs a callback to process its command-line options
# - Command.__init__() needs access to its share of the whole
@@ -90,15 +86,13 @@
"forcibly build everything (ignore file timestamps)"),
('compiler=', 'c',
"specify the compiler type"),
- ('swig-cpp', None,
- "make SWIG create C++ files (default is C)"),
('swig-opts=', None,
"list of SWIG command-line options"),
('swig=', None,
"path to the SWIG executable"),
]
- boolean_options = ['inplace', 'debug', 'force', 'swig-cpp']
+ boolean_options = ['inplace', 'debug', 'force']
if HAS_USER_SITE:
user_options.append(('user', None,
@@ -110,46 +104,12 @@
"list available compilers", show_compilers),
]
-
- # making 'compiler' a property to deprecate
- # its usage as something else than a compiler type
- # e.g. like a compiler instance
- def __init__(self, dist):
- self._compiler = None
- Command.__init__(self, dist)
-
- def __setattr__(self, name, value):
- # need this to make sure setattr() (used in distutils)
- # doesn't kill our property
- if name == 'compiler':
- self._set_compiler(value)
- else:
- self.__dict__[name] = value
-
- def _set_compiler(self, compiler):
- if not isinstance(compiler, str) and compiler is not None:
- # we don't want to allow that anymore in the future
- warn("'compiler' specifies the compiler type in build_ext. "
- "If you want to get the compiler object itself, "
- "use 'compiler_obj'", DeprecationWarning)
- self._compiler = compiler
-
- def _get_compiler(self):
- if not isinstance(self._compiler, str) and self._compiler is not None:
- # we don't want to allow that anymore in the future
- warn("'compiler' specifies the compiler type in build_ext. "
- "If you want to get the compiler object itself, "
- "use 'compiler_obj'", DeprecationWarning)
- return self._compiler
-
- compiler = property(_get_compiler, _set_compiler)
-
def initialize_options(self):
self.extensions = None
self.build_lib = None
self.plat_name = None
self.build_temp = None
- self.inplace = 0
+ self.inplace = False
self.package = None
self.include_dirs = None
@@ -163,7 +123,6 @@
self.force = None
self.compiler = None
self.swig = None
- self.swig_cpp = None
self.swig_opts = None
if HAS_USER_SITE:
self.user = None
@@ -183,7 +142,7 @@
if not isinstance(self.extensions, (list, tuple)):
type_name = (self.extensions is None and 'None'
or type(self.extensions).__name__)
- raise DistutilsSetupError(
+ raise PackagingSetupError(
"'ext_modules' must be a sequence of Extension instances,"
" not %s" % (type_name,))
for i, ext in enumerate(self.extensions):
@@ -191,7 +150,7 @@
continue # OK! (assume type-checking done
# by Extension constructor)
type_name = (ext is None and 'None' or type(ext).__name__)
- raise DistutilsSetupError(
+ raise PackagingSetupError(
"'ext_modules' item %d must be an Extension instance,"
" not %s" % (i, type_name))
@@ -201,7 +160,7 @@
plat_py_include = sysconfig.get_path('platinclude')
if self.include_dirs is None:
self.include_dirs = self.distribution.include_dirs or []
- if isinstance(self.include_dirs, str):
+ if isinstance(self.include_dirs, basestring):
self.include_dirs = self.include_dirs.split(os.pathsep)
# Put the Python "system" include dir at the end, so that
@@ -210,7 +169,7 @@
if plat_py_include != py_include:
self.include_dirs.append(plat_py_include)
- if isinstance(self.libraries, str):
+ if isinstance(self.libraries, basestring):
self.libraries = [self.libraries]
# Life is easier if we're not forever checking for None, so
@@ -219,12 +178,12 @@
self.libraries = []
if self.library_dirs is None:
self.library_dirs = []
- elif isinstance(self.library_dirs, str):
+ elif isinstance(self.library_dirs, basestring):
self.library_dirs = self.library_dirs.split(os.pathsep)
if self.rpath is None:
self.rpath = []
- elif isinstance(self.rpath, str):
+ elif isinstance(self.rpath, basestring):
self.rpath = self.rpath.split(os.pathsep)
# for extensions under windows use different directories
@@ -317,8 +276,8 @@
# Finally add the user include and library directories if requested
if HAS_USER_SITE and self.user:
- user_include = os.path.join(USER_BASE, "include")
- user_lib = os.path.join(USER_BASE, "lib")
+ user_include = os.path.join(site.USER_BASE, "include")
+ user_lib = os.path.join(site.USER_BASE, "lib")
if os.path.isdir(user_include):
self.include_dirs.append(user_include)
if os.path.isdir(user_lib):
@@ -328,18 +287,6 @@
def run(self):
from distutils2.compiler import new_compiler
- # 'self.extensions', as supplied by setup.py, is a list of
- # Extension instances. See the documentation for Extension (in
- # distutils.extension) for details.
- #
- # For backwards compatibility with Distutils 0.8.2 and earlier, we
- # also allow the 'extensions' list to be a list of tuples:
- # (ext_name, build_info)
- # where build_info is a dictionary containing everything that
- # Extension instances do except the name, with a few things being
- # differently named. We convert these 2-tuples to Extension
- # instances as needed.
-
if not self.extensions:
return
@@ -351,24 +298,17 @@
self.libraries.extend(build_clib.get_library_names() or [])
self.library_dirs.append(build_clib.build_clib)
+ # Temporary kludge until we remove the verbose arguments and use
+ # logging everywhere
+ verbose = logger.getEffectiveLevel() >= logging.DEBUG
+
# Setup the CCompiler object that we'll use to do all the
# compiling and linking
-
- # used to prevent the usage of an existing compiler for the
- # compiler option when calling new_compiler()
- # this will be removed in 3.3 and 2.8
- if not isinstance(self._compiler, str):
- self._compiler = None
-
- self.compiler_obj = new_compiler(compiler=self._compiler,
- verbose=self.verbose,
+ self.compiler_obj = new_compiler(compiler=self.compiler,
+ verbose=verbose,
dry_run=self.dry_run,
force=self.force)
- # used to keep the compiler object reachable with
- # "self.compiler". this will be removed in 3.3 and 2.8
- self._compiler = self.compiler_obj
-
customize_compiler(self.compiler_obj)
# If we are cross-compiling, init the compiler now (if we are not
# cross-compiling, init would not hurt, but people may rely on
@@ -384,7 +324,7 @@
self.compiler_obj.set_include_dirs(self.include_dirs)
if self.define is not None:
# 'define' option is a list of (name,value) tuples
- for (name, value) in self.define:
+ for name, value in self.define:
self.compiler_obj.define_macro(name, value)
if self.undef is not None:
for macro in self.undef:
@@ -423,19 +363,19 @@
for ext in self.extensions:
try:
self.build_extension(ext)
- except (CCompilerError, DistutilsError, CompileError), e:
+ except (CCompilerError, PackagingError, CompileError):
if not ext.optional:
raise
- self.warn('building extension "%s" failed: %s' %
- (ext.name, e))
+ logger.warning('%s: building extension %r failed: %s',
+ self.get_command_name(), ext.name,
+ sys.exc_info()[1])
def build_extension(self, ext):
sources = ext.sources
if sources is None or not isinstance(sources, (list, tuple)):
- raise DistutilsSetupError, \
- ("in 'ext_modules' option (extension '%s'), " +
+ raise PackagingSetupError(("in 'ext_modules' option (extension '%s'), " +
"'sources' must be present and must be " +
- "a list of source filenames") % ext.name
+ "a list of source filenames") % ext.name)
sources = list(sources)
ext_path = self.get_ext_fullpath(ext.name)
@@ -484,7 +424,7 @@
# The setup.py script for Python on Unix needs to be able to
# get this list so it can perform all the clean up needed to
# avoid keeping object files around when cleaning out a failed
- # build of an extension module. Since Distutils does not
+ # build of an extension module. Since Packaging does not
# track dependencies, we have to get rid of intermediates to
# ensure all the intermediates will be properly re-built.
#
@@ -527,17 +467,13 @@
# source -- but there should be an option to put SWIG output in
# the temp dir.
- if self.swig_cpp:
- logger.warn("--swig-cpp is deprecated - use --swig-opts=-c++")
-
- if self.swig_cpp or ('-c++' in self.swig_opts) or \
- ('-c++' in extension.swig_opts):
+ if ('-c++' in self.swig_opts or '-c++' in extension.swig_opts):
target_ext = '.cpp'
else:
target_ext = '.c'
for source in sources:
- (base, ext) = os.path.splitext(source)
+ base, ext = os.path.splitext(source)
if ext == ".i": # SWIG interface file
new_sources.append(base + '_wrap' + target_ext)
swig_sources.append(source)
@@ -551,8 +487,6 @@
swig = self.swig or self.find_swig()
swig_cmd = [swig, "-python"]
swig_cmd.extend(self.swig_opts)
- if self.swig_cpp:
- swig_cmd.append("-c++")
# Do not override commandline arguments
if not self.swig_opts:
@@ -591,9 +525,8 @@
return "swig.exe"
else:
- raise DistutilsPlatformError, \
- ("I don't know how to find (much less run) SWIG "
- "on platform '%s'") % os.name
+ raise PackagingPlatformError(("I don't know how to find (much less run) SWIG "
+ "on platform '%s'") % os.name)
# -- Name generators -----------------------------------------------
# (extension names, filenames, whatever)
@@ -654,7 +587,7 @@
provided, "init" + module_name. Only relevant on Windows, where
the .pyd file (DLL) must export the module "init" function.
"""
- initfunc_name = "init" + ext.name.split('.')[-1]
+ initfunc_name = "PyInit_" + ext.name.split('.')[-1]
if initfunc_name not in ext.export_symbols:
ext.export_symbols.append(initfunc_name)
return ext.export_symbols
@@ -723,9 +656,8 @@
else:
if sysconfig.get_config_var('Py_ENABLE_SHARED'):
- template = "python%d.%d"
- pythonlib = (template %
- (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff))
+ pythonlib = 'python{}.{}'.format(
+ sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)
return ext.libraries + [pythonlib]
else:
return ext.libraries
diff --git a/distutils2/command/build_py.py b/distutils2/command/build_py.py
--- a/distutils2/command/build_py.py
+++ b/distutils2/command/build_py.py
@@ -1,15 +1,12 @@
-"""distutils.command.build_py
-
-Implements the Distutils 'build_py' command."""
-
+"""Build pure Python modules (just copy to build directory)."""
import os
import sys
-import logging
from glob import glob
+from distutils2 import logger
from distutils2.command.cmd import Command
-from distutils2.errors import DistutilsOptionError, DistutilsFileError
+from distutils2.errors import PackagingOptionError, PackagingFileError
from distutils2.util import convert_path
from distutils2.compat import Mixin2to3
@@ -18,10 +15,10 @@
class build_py(Command, Mixin2to3):
- description = "\"build\" pure Python modules (copy to build directory)"
+ description = "build pure Python modules (copy to build directory)"
user_options = [
- ('build-lib=', 'd', "directory to \"build\" (copy) to"),
+ ('build-lib=', 'd', "directory to build (copy) to"),
('compile', 'c', "compile .py to .pyc"),
('no-compile', None, "don't compile .py files [default]"),
('optimize=', 'O',
@@ -45,7 +42,7 @@
self.package = None
self.package_data = None
self.package_dir = None
- self.compile = 0
+ self.compile = False
self.optimize = 0
self.force = None
self._updated_files = []
@@ -77,7 +74,7 @@
self.optimize = int(self.optimize)
assert 0 <= self.optimize <= 2
except (ValueError, AssertionError):
- raise DistutilsOptionError("optimize must be 0, 1, or 2")
+ raise PackagingOptionError("optimize must be 0, 1, or 2")
def run(self):
# XXX copy_file by default preserves atime and mtime. IMHO this is
@@ -111,7 +108,7 @@
self.run_2to3(self._updated_files, self._doctests_2to3,
self.use_2to3_fixers)
- self.byte_compile(self.get_outputs(include_bytecode=0))
+ self.byte_compile(self.get_outputs(include_bytecode=False))
# -- Top-level worker functions ------------------------------------
@@ -154,7 +151,7 @@
# Each pattern has to be converted to a platform-specific path
filelist = glob(os.path.join(src_dir, convert_path(pattern)))
# Files that match more than one pattern are only added once
- files.extend([fn for fn in filelist if fn not in files])
+ files.extend(fn for fn in filelist if fn not in files)
return files
def build_package_data(self):
@@ -179,6 +176,7 @@
"""Return the directory, relative to the top of the source
distribution, where package 'package' should be found
(at least according to the 'package_dir' option, if any)."""
+
path = package.split('.')
if self.package_dir is not None:
path.insert(0, self.package_dir)
@@ -197,10 +195,10 @@
# circumvent them.
if package_dir != "":
if not os.path.exists(package_dir):
- raise DistutilsFileError(
+ raise PackagingFileError(
"package directory '%s' does not exist" % package_dir)
if not os.path.isdir(package_dir):
- raise DistutilsFileError(
+ raise PackagingFileError(
"supposed package directory '%s' exists, "
"but is not a directory" % package_dir)
@@ -210,8 +208,8 @@
if os.path.isfile(init_py):
return init_py
else:
- logging.warning(("package init file '%s' not found " +
- "(or not a regular file)"), init_py)
+ logger.warning(("package init file '%s' not found " +
+ "(or not a regular file)"), init_py)
# Either not in a package at all (__init__.py not expected), or
# __init__.py doesn't exist -- so don't return the filename.
@@ -219,8 +217,8 @@
def check_module(self, module, module_file):
if not os.path.isfile(module_file):
- logging.warning("file %s (for module %s) not found",
- module_file, module)
+ logger.warning("file %s (for module %s) not found",
+ module_file, module)
return False
else:
return True
@@ -240,7 +238,7 @@
module = os.path.splitext(os.path.basename(f))[0]
modules.append((package, module, f))
else:
- self.debug_print("excluding %s" % setup_script)
+ logger.debug("excluding %s", setup_script)
return modules
def find_modules(self):
@@ -273,10 +271,10 @@
module_base = path[-1]
try:
- (package_dir, checked) = packages[package]
+ package_dir, checked = packages[package]
except KeyError:
package_dir = self.get_package_dir(package)
- checked = 0
+ checked = False
if not checked:
init_py = self.check_package(package, package_dir)
@@ -323,10 +321,10 @@
outfile_path = [build_dir] + list(package) + [module + ".py"]
return os.path.join(*outfile_path)
- def get_outputs(self, include_bytecode=1):
+ def get_outputs(self, include_bytecode=True):
modules = self.find_all_modules()
outputs = []
- for (package, module, module_file) in modules:
+ for package, module, module_file in modules:
package = package.split('.')
filename = self.get_module_outfile(self.build_lib, package, module)
outputs.append(filename)
@@ -344,7 +342,7 @@
return outputs
def build_module(self, module, module_file, package):
- if isinstance(package, str):
+ if isinstance(package, basestring):
package = package.split('.')
elif not isinstance(package, (list, tuple)):
raise TypeError(
@@ -356,11 +354,11 @@
outfile = self.get_module_outfile(self.build_lib, package, module)
dir = os.path.dirname(outfile)
self.mkpath(dir)
- return self.copy_file(module_file, outfile, preserve_mode=0)
+ return self.copy_file(module_file, outfile, preserve_mode=False)
def build_modules(self):
modules = self.find_modules()
- for (package, module, module_file) in modules:
+ for package, module, module_file in modules:
# Now "build" the module -- ie. copy the source file to
# self.build_lib (the build directory for Python source).
@@ -385,13 +383,14 @@
# Now loop over the modules we found, "building" each one (just
# copy it to self.build_lib).
- for (package_, module, module_file) in modules:
+ for package_, module, module_file in modules:
assert package == package_
self.build_module(module, module_file, package)
def byte_compile(self, files):
if hasattr(sys, 'dont_write_bytecode') and sys.dont_write_bytecode:
- self.warn('byte-compiling is disabled, skipping.')
+ logger.warning('%s: byte-compiling is disabled, skipping.',
+ self.get_command_name())
return
from distutils2.util import byte_compile
diff --git a/distutils2/command/build_scripts.py b/distutils2/command/build_scripts.py
--- a/distutils2/command/build_scripts.py
+++ b/distutils2/command/build_scripts.py
@@ -1,26 +1,24 @@
-"""distutils.command.build_scripts
+"""Build scripts (copy to build dir and fix up shebang line)."""
-Implements the Distutils 'build_scripts' command."""
-
-
-import os, re
-from stat import ST_MODE
+import os
+import re
+import sysconfig
from distutils2.command.cmd import Command
-from distutils2.util import convert_path, newer
+from distutils2.util import convert_path, newer, detect_encoding, fsencode
from distutils2 import logger
-from distutils2._backport import sysconfig
from distutils2.compat import Mixin2to3
+
# check if Python is called on the first line with this expression
-first_line_re = re.compile('^#!.*python[0-9.]*([ \t].*)?$')
+first_line_re = re.compile(b'^#!.*python[0-9.]*([ \t].*)?$')
-class build_scripts (Command, Mixin2to3):
+class build_scripts(Command, Mixin2to3):
- description = "\"build\" scripts (copy and fixup #! line)"
+ description = "build scripts (copy and fix up shebang line)"
user_options = [
- ('build-dir=', 'd', "directory to \"build\" (copy) to"),
+ ('build-dir=', 'd', "directory to build (copy) to"),
('force', 'f', "forcibly build everything (ignore file timestamps"),
('executable=', 'e', "specify final destination interpreter path"),
]
@@ -28,7 +26,7 @@
boolean_options = ['force']
- def initialize_options (self):
+ def initialize_options(self):
self.build_dir = None
self.scripts = None
self.force = None
@@ -38,7 +36,7 @@
self.convert_2to3_doctests = None
self.use_2to3_fixers = None
- def finalize_options (self):
+ def finalize_options(self):
self.set_undefined_options('build',
('build_scripts', 'build_dir'),
'use_2to3', 'use_2to3_fixers',
@@ -49,14 +47,14 @@
def get_source_files(self):
return self.scripts
- def run (self):
+ def run(self):
if not self.scripts:
return
copied_files = self.copy_scripts()
- if self.use_2to3 and self.copied_files:
- self._run_2to3(self.copied_files, fixers=self.use_2to3_fixers)
+ if self.use_2to3 and copied_files:
+ self._run_2to3(copied_files, fixers=self.use_2to3_fixers)
- def copy_scripts (self):
+ def copy_scripts(self):
"""Copy each script listed in 'self.scripts'; if it's marked as a
Python script in the Unix way (first line matches 'first_line_re',
ie. starts with "\#!" and contains "python"), then adjust the first
@@ -65,7 +63,7 @@
self.mkpath(self.build_dir)
outfiles = []
for script in self.scripts:
- adjust = 0
+ adjust = False
script = convert_path(script)
outfile = os.path.join(self.build_dir, os.path.basename(script))
outfiles.append(outfile)
@@ -78,40 +76,62 @@
# that way, we'll get accurate feedback if we can read the
# script.
try:
- f = open(script, "r")
+ f = open(script, "rb")
except IOError:
if not self.dry_run:
raise
f = None
else:
+ encoding, lines = detect_encoding(f.readline)
+ f.seek(0)
first_line = f.readline()
if not first_line:
- self.warn("%s is an empty file (skipping)" % script)
+ logger.warning('%s: %s is an empty file (skipping)',
+ self.get_command_name(), script)
continue
match = first_line_re.match(first_line)
if match:
- adjust = 1
- post_interp = match.group(1) or ''
+ adjust = True
+ post_interp = match.group(1) or b''
if adjust:
logger.info("copying and adjusting %s -> %s", script,
self.build_dir)
if not self.dry_run:
- outf = open(outfile, "w")
if not sysconfig.is_python_build():
- outf.write("#!%s%s\n" %
- (self.executable,
- post_interp))
+ executable = self.executable
else:
- outf.write("#!%s%s\n" %
- (os.path.join(
+ executable = os.path.join(
sysconfig.get_config_var("BINDIR"),
"python%s%s" % (sysconfig.get_config_var("VERSION"),
- sysconfig.get_config_var("EXE"))),
- post_interp))
- outf.writelines(f.readlines())
- outf.close()
+ sysconfig.get_config_var("EXE")))
+ executable = fsencode(executable)
+ shebang = b"#!" + executable + post_interp + b"\n"
+ # 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:
+ raise ValueError(
+ "The shebang ({!r}) is not decodable "
+ "from utf-8".format(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.
+ try:
+ shebang.decode(encoding)
+ except UnicodeDecodeError:
+ raise ValueError(
+ "The shebang ({!r}) is not decodable "
+ "from the script encoding ({})"
+ .format(shebang, encoding))
+ with open(outfile, "wb") as outf:
+ outf.write(shebang)
+ outf.writelines(f.readlines())
if f:
f.close()
else:
@@ -124,13 +144,10 @@
if self.dry_run:
logger.info("changing mode of %s", file)
else:
- oldmode = os.stat(file)[ST_MODE] & 07777
- newmode = (oldmode | 0555) & 07777
+ oldmode = os.stat(file).st_mode & 0o7777
+ newmode = (oldmode | 0o555) & 0o7777
if newmode != oldmode:
logger.info("changing mode of %s from %o to %o",
file, oldmode, newmode)
os.chmod(file, newmode)
return outfiles
- # copy_scripts ()
-
-# class build_scripts
diff --git a/distutils2/command/check.py b/distutils2/command/check.py
--- a/distutils2/command/check.py
+++ b/distutils2/command/check.py
@@ -1,16 +1,14 @@
-"""distutils.command.check
+"""Check PEP compliance of metadata."""
-Implements the Distutils 'check' command.
-"""
-
+from distutils2 import logger
from distutils2.command.cmd import Command
-from distutils2.errors import DistutilsSetupError
+from distutils2.errors import PackagingSetupError
from distutils2.util import resolve_name
class check(Command):
- """This command checks the metadata of the package.
- """
- description = ("perform some checks on the package")
+
+ description = "check PEP compliance of metadata"
+
user_options = [('metadata', 'm', 'Verify metadata'),
('all', 'a',
('runs extended set of checks')),
@@ -21,18 +19,20 @@
def initialize_options(self):
"""Sets default values for options."""
- self.all = 0
- self.metadata = 1
- self.strict = 0
+ self.all = False
+ self.metadata = True
+ self.strict = False
self._warnings = []
def finalize_options(self):
pass
- def warn(self, msg):
- """Counts the number of warnings that occurs."""
- self._warnings.append(msg)
- return Command.warn(self, msg)
+ def warn(self, msg, *args):
+ """Wrapper around logging that also remembers messages."""
+ # XXX we could use a special handler for this, but would need to test
+ # if it works even if the logger has a too high level
+ self._warnings.append((msg, args))
+ return logger.warning('%s: %s' % (self.get_command_name(), msg), *args)
def run(self):
"""Runs the command."""
@@ -46,8 +46,8 @@
# let's raise an error in strict mode, if we have at least
# one warning
if self.strict and len(self._warnings) > 0:
- msg = '\n'.join(self._warnings)
- raise DistutilsSetupError(msg)
+ msg = '\n'.join(msg % args for msg, args in self._warnings)
+ raise PackagingSetupError(msg)
def check_metadata(self):
"""Ensures that all required elements of metadata are supplied.
@@ -58,7 +58,7 @@
"""
missing, warnings = self.distribution.metadata.check(strict=True)
if missing != []:
- self.warn("missing required metadata: %s" % ', '.join(missing))
+ self.warn('missing required metadata: %s', ', '.join(missing))
for warning in warnings:
self.warn(warning)
@@ -74,15 +74,15 @@
warning = '%s (line %s)' % (warning[1], line)
self.warn(warning)
elif self.strict:
- raise DistutilsSetupError('The docutils package is needed.')
+ raise PackagingSetupError('The docutils package is needed.')
def check_hooks_resolvable(self):
- for options in self.distribution.command_options.itervalues():
+ for options in self.distribution.command_options.values():
for hook_kind in ("pre_hook", "post_hook"):
if hook_kind not in options:
break
- for hook_name in options[hook_kind][1].itervalues():
+ for hook_name in options[hook_kind][1].values():
try:
resolve_name(hook_name)
except ImportError:
- self.warn("Name '%s' cannot be resolved." % hook_name)
+ self.warn('name %r cannot be resolved', hook_name)
diff --git a/distutils2/command/clean.py b/distutils2/command/clean.py
--- a/distutils2/command/clean.py
+++ b/distutils2/command/clean.py
@@ -1,9 +1,6 @@
-"""distutils.command.clean
+"""Clean up temporary files created by the build command."""
-Implements the Distutils 'clean' command."""
-
-# contributed by Bastian Kleineidam <calvin(a)cs.uni-sb.de>, added 2000-03-18
-
+# Contributed by Bastian Kleineidam <calvin(a)cs.uni-sb.de>
import os
from shutil import rmtree
@@ -66,7 +63,7 @@
else:
rmtree(directory)
else:
- logger.warn("'%s' does not exist -- can't clean it",
+ logger.warning("'%s' does not exist -- can't clean it",
directory)
# just for the heck of it, try to remove the base build directory:
@@ -77,5 +74,3 @@
logger.info("removing '%s'", self.build_base)
except OSError:
pass
-
-# class clean
diff --git a/distutils2/command/cmd.py b/distutils2/command/cmd.py
--- a/distutils2/command/cmd.py
+++ b/distutils2/command/cmd.py
@@ -1,21 +1,16 @@
-"""distutils.cmd
+"""Base class for commands."""
-Provides the Command class, the base class for the command classes
-in the distutils.command package.
-"""
import os
import re
-import logging
-
-from distutils2.errors import DistutilsOptionError
+from shutil import copyfile, move
from distutils2 import util
from distutils2 import logger
-from distutils2._backport.shutil import copytree, copyfile, move, make_archive
+from distutils2.errors import PackagingOptionError
class Command(object):
"""Abstract base class for defining command classes, the "worker bees"
- of the Distutils. A useful analogy for command classes is to think of
+ of the Packaging. A useful analogy for command classes is to think of
them as subroutines with local variables called "options". The options
are "declared" in 'initialize_options()' and "defined" (given their
final values, aka "finalized") in 'finalize_options()', both of which
@@ -62,7 +57,8 @@
from distutils2.dist import Distribution
if not isinstance(dist, Distribution):
- raise TypeError("dist must be a Distribution instance")
+ raise TypeError("dist must be an instance of Distribution, not %r"
+ % type(dist))
if self.__class__ is Command:
raise RuntimeError("Command is an abstract class")
@@ -70,7 +66,7 @@
self.initialize_options()
# Per-command versions of the global flags, so that the user can
- # customize Distutils' behaviour command-by-command and let some
+ # customize Packaging' behaviour command-by-command and let some
# commands fall back on the Distribution's behaviour. None means
# "not defined, check self.distribution's copy", while 0 or 1 mean
# false and true (duh). Note that this means figuring out the real
@@ -80,10 +76,6 @@
# "fix" it?]
self._dry_run = None
- # verbose is largely ignored, but needs to be set for
- # backwards compatibility (I think)?
- self.verbose = dist.verbose
-
# Some commands define a 'self.force' option to ignore file
# timestamps, but methods defined *here* assume that
# 'self.force' exists for all commands. So define it here
@@ -92,13 +84,13 @@
# The 'help' flag is just used for command line parsing, so
# none of that complicated bureaucracy is needed.
- self.help = 0
+ self.help = False
# 'finalized' records whether or not 'finalize_options()' has been
# called. 'finalize_options()' itself should not pay attention to
# this flag: it is the business of 'ensure_finalized()', which
# always calls 'finalize_options()', to respect/update it.
- self.finalized = 0
+ self.finalized = False
# XXX A more explicit way to customize dry_run would be better.
@property
@@ -111,7 +103,7 @@
def ensure_finalized(self):
if not self.finalized:
self.finalize_options()
- self.finalized = 1
+ self.finalized = True
# Subclasses must define:
# initialize_options()
@@ -156,18 +148,17 @@
def dump_options(self, header=None, indent=""):
if header is None:
header = "command options for '%s':" % self.get_command_name()
- self.announce(indent + header, level=logging.INFO)
+ logger.info(indent + header)
indent = indent + " "
negative_opt = getattr(self, 'negative_opt', ())
- for (option, _, _) in self.user_options:
+ for option, _, _ in self.user_options:
if option in negative_opt:
continue
option = option.replace('-', '_')
if option[-1] == "=":
option = option[:-1]
value = getattr(self, option)
- self.announce(indent + "%s = %s" % (option, value),
- level=logging.INFO)
+ logger.info(indent + "%s = %s", option, value)
def run(self):
"""A command's raison d'etre: carry out the action it exists to
@@ -182,13 +173,6 @@
raise RuntimeError(
"abstract method -- subclass %s must override" % self.__class__)
- # TODO remove this method, just use logging.info
- def announce(self, msg, level=logging.INFO):
- """If the current verbosity level is of greater than or equal to
- 'level' print 'msg' to stdout.
- """
- logger.log(level, msg)
-
# -- External interface --------------------------------------------
# (called by outsiders)
@@ -221,7 +205,7 @@
# value meets certain type and value constraints. If not, we try to
# force it into conformance (eg. if we expect a list but have a string,
# split the string on comma and/or whitespace). If we can't force the
- # option into conformance, raise DistutilsOptionError. Thus, command
+ # option into conformance, raise PackagingOptionError. Thus, command
# classes need do nothing more than (eg.)
# self.ensure_string_list('foo')
# and they can be guaranteed that thereafter, self.foo will be
@@ -232,8 +216,8 @@
if val is None:
setattr(self, option, default)
return default
- elif not isinstance(val, str):
- raise DistutilsOptionError("'%s' must be a %s (got `%s`)" %
+ elif not isinstance(val, basestring):
+ raise PackagingOptionError("'%s' must be a %s (got `%s`)" %
(option, what, val))
return val
@@ -252,28 +236,28 @@
val = getattr(self, option)
if val is None:
return
- elif isinstance(val, str):
+ elif isinstance(val, basestring):
setattr(self, option, re.split(r',\s*|\s+', val))
else:
if isinstance(val, list):
# checks if all elements are str
- ok = 1
+ ok = True
for element in val:
- if not isinstance(element, str):
- ok = 0
+ if not isinstance(element, basestring):
+ ok = False
break
else:
- ok = 0
+ ok = False
if not ok:
- raise DistutilsOptionError(
+ raise PackagingOptionError(
"'%s' must be a list of strings (got %r)" % (option, val))
def _ensure_tested_string(self, option, tester,
what, error_fmt, default=None):
val = self._ensure_stringlike(option, what, default)
if val is not None and not tester(val):
- raise DistutilsOptionError(
+ raise PackagingOptionError(
("error in '%s' option: " + error_fmt) % (option, val))
def ensure_filename(self, option):
@@ -324,7 +308,7 @@
setattr(self, dst_option,
getattr(src_cmd_obj, src_option))
- def get_finalized_command(self, command, create=1):
+ def get_finalized_command(self, command, create=True):
"""Wrapper around Distribution's 'get_command_obj()' method: find
(create if necessary and 'create' is true) the command object for
'command', call its 'ensure_finalized()' method, and return the
@@ -334,7 +318,7 @@
cmd_obj.ensure_finalized()
return cmd_obj
- def get_reinitialized_command(self, command, reinit_subcommands=0):
+ def get_reinitialized_command(self, command, reinit_subcommands=False):
return self.distribution.get_reinitialized_command(
command, reinit_subcommands)
@@ -364,14 +348,10 @@
# -- External world manipulation -----------------------------------
- # TODO remove this method, just use logging.warn
- def warn(self, msg):
- logger.warning("warning: %s: %s\n", self.get_command_name(), msg)
-
def execute(self, func, args, msg=None, level=1):
util.execute(func, args, msg, dry_run=self.dry_run)
- def mkpath(self, name, mode=0777, dry_run=None, verbose=0):
+ def mkpath(self, name, mode=0o777, dry_run=None, verbose=0):
if dry_run is None:
dry_run = self.dry_run
name = os.path.normpath(name)
@@ -386,7 +366,7 @@
os.makedirs(name, mode)
def copy_file(self, infile, outfile,
- preserve_mode=1, preserve_times=1, link=None, level=1):
+ preserve_mode=True, preserve_times=True, link=None, level=1):
"""Copy a file respecting verbose, dry-run and force flags. (The
former two default to whatever is in the Distribution object, and
the latter defaults to false for commands that don't define it.)"""
@@ -398,32 +378,34 @@
copyfile(infile, outfile)
return outfile, None # XXX
- def copy_tree(self, infile, outfile,
- preserve_mode=1, preserve_times=1, preserve_symlinks=0,
- level=1):
+ def copy_tree(self, infile, outfile, preserve_mode=True,
+ preserve_times=True, preserve_symlinks=False, level=1):
"""Copy an entire directory tree respecting verbose, dry-run,
and force flags.
"""
if self.dry_run:
return # see if we want to display something
- return copytree(infile, outfile, preserve_symlinks)
+
+
+ return util.copy_tree(infile, outfile, preserve_mode, preserve_times,
+ preserve_symlinks, not self.force, dry_run=self.dry_run)
def move_file(self, src, dst, level=1):
- """Move a file respectin dry-run flag."""
+ """Move a file respecting the dry-run flag."""
if self.dry_run:
return # XXX log ?
return move(src, dst)
- def spawn(self, cmd, search_path=1, level=1):
+ def spawn(self, cmd, search_path=True, level=1):
"""Spawn an external command respecting dry-run flag."""
from distutils2.util import spawn
spawn(cmd, search_path, dry_run=self.dry_run)
def make_archive(self, base_name, format, root_dir=None, base_dir=None,
owner=None, group=None):
- return make_archive(base_name, format, root_dir,
- base_dir, dry_run=self.dry_run,
- owner=owner, group=group)
+ return util.make_archive(base_name, format, root_dir,
+ base_dir, dry_run=self.dry_run,
+ owner=owner, group=group)
def make_file(self, infiles, outfile, func, args,
exec_msg=None, skip_msg=None, level=1):
@@ -439,7 +421,7 @@
skip_msg = "skipping %s (inputs unchanged)" % outfile
# Allow 'infiles' to be a single string
- if isinstance(infiles, str):
+ if isinstance(infiles, basestring):
infiles = (infiles,)
elif not isinstance(infiles, (list, tuple)):
raise TypeError(
diff --git a/distutils2/command/command_template b/distutils2/command/command_template
--- a/distutils2/command/command_template
+++ b/distutils2/command/command_template
@@ -1,7 +1,7 @@
-"""Implementation of the 'x' command."""
+"""Do X and Y."""
-import logging
-from distutils2.command.cmd import Command
+from packaging import logger
+from packaging.command.cmd import Command
class x(Command):
@@ -12,11 +12,10 @@
# List of option tuples: long name, short name (None if no short
# name), and help string.
user_options = [
- ('', '', # long option, short option (one letter) or None
- ""), # help text
+ ('', '', # long option, short option (one letter) or None
+ ""), # help text
]
-
def initialize_options(self):
self. = None
self. = None
@@ -28,7 +27,7 @@
def run(self):
...
- logging.info(...)
+ logger.info(...)
if not self.dry_run:
...
diff --git a/distutils2/command/config.py b/distutils2/command/config.py
--- a/distutils2/command/config.py
+++ b/distutils2/command/config.py
@@ -1,6 +1,6 @@
-"""distutils.command.config
+"""Prepare the build.
-Implements the Distutils 'config' command, a (mostly) empty command class
+This module provides config, a (mostly) empty command class
that exists mainly to be sub-classed by specific module distributions and
applications. The idea is that while every "config" command is different,
at least they're all named the same, and users always see "config" in the
@@ -8,11 +8,12 @@
configure-like tasks: "try to compile this C code", or "figure out where
this header file lives".
"""
+
import os
import re
from distutils2.command.cmd import Command
-from distutils2.errors import DistutilsExecError
+from distutils2.errors import PackagingExecError
from distutils2.compiler import customize_compiler
from distutils2 import logger
@@ -20,7 +21,7 @@
class config(Command):
- description = "prepare to build"
+ description = "prepare the build"
user_options = [
('compiler=', None,
@@ -56,8 +57,8 @@
self.library_dirs = None
# maximal output for now
- self.noisy = 1
- self.dump_source = 1
+ self.noisy = True
+ self.dump_source = True
# list of temporary files generated along-the-way that we have
# to clean at some point
@@ -66,17 +67,17 @@
def finalize_options(self):
if self.include_dirs is None:
self.include_dirs = self.distribution.include_dirs or []
- elif isinstance(self.include_dirs, str):
+ elif isinstance(self.include_dirs, basestring):
self.include_dirs = self.include_dirs.split(os.pathsep)
if self.libraries is None:
self.libraries = []
- elif isinstance(self.libraries, str):
+ elif isinstance(self.libraries, basestring):
self.libraries = [self.libraries]
if self.library_dirs is None:
self.library_dirs = []
- elif isinstance(self.library_dirs, str):
+ elif isinstance(self.library_dirs, basestring):
self.library_dirs = self.library_dirs.split(os.pathsep)
def run(self):
@@ -97,7 +98,7 @@
from distutils2.compiler import new_compiler
if not isinstance(self.compiler, CCompiler):
self.compiler = new_compiler(compiler=self.compiler,
- dry_run=self.dry_run, force=1)
+ dry_run=self.dry_run, force=True)
customize_compiler(self.compiler)
if self.include_dirs:
self.compiler.set_include_dirs(self.include_dirs)
@@ -109,36 +110,35 @@
def _gen_temp_sourcefile(self, body, headers, lang):
filename = "_configtest" + LANG_EXT[lang]
- file = open(filename, "w")
- if headers:
- for header in headers:
- file.write("#include <%s>\n" % header)
- file.write("\n")
- file.write(body)
- if body[-1] != "\n":
- file.write("\n")
- file.close()
+ with open(filename, "w") as file:
+ if headers:
+ for header in headers:
+ file.write("#include <%s>\n" % header)
+ file.write("\n")
+ file.write(body)
+ if body[-1] != "\n":
+ file.write("\n")
return filename
def _preprocess(self, body, headers, include_dirs, lang):
src = self._gen_temp_sourcefile(body, headers, lang)
out = "_configtest.i"
- self.temp_files.extend([src, out])
+ self.temp_files.extend((src, out))
self.compiler.preprocess(src, out, include_dirs=include_dirs)
- return (src, out)
+ return src, out
def _compile(self, body, headers, include_dirs, lang):
src = self._gen_temp_sourcefile(body, headers, lang)
if self.dump_source:
dump_file(src, "compiling '%s':" % src)
- (obj,) = self.compiler.object_filenames([src])
- self.temp_files.extend([src, obj])
+ obj = self.compiler.object_filenames([src])[0]
+ self.temp_files.extend((src, obj))
self.compiler.compile([src], include_dirs=include_dirs)
- return (src, obj)
+ return src, obj
def _link(self, body, headers, include_dirs, libraries, library_dirs,
lang):
- (src, obj) = self._compile(body, headers, include_dirs, lang)
+ src, obj = self._compile(body, headers, include_dirs, lang)
prog = os.path.splitext(os.path.basename(src))[0]
self.compiler.link_executable([obj], prog,
libraries=libraries,
@@ -149,7 +149,7 @@
prog = prog + self.compiler.exe_extension
self.temp_files.append(prog)
- return (src, obj, prog)
+ return src, obj, prog
def _clean(self, *filenames):
if not filenames:
@@ -182,11 +182,11 @@
"""
from distutils2.compiler.ccompiler import CompileError
self._check_compiler()
- ok = 1
+ ok = True
try:
self._preprocess(body, headers, include_dirs, lang)
except CompileError:
- ok = 0
+ ok = False
self._clean()
return ok
@@ -203,20 +203,19 @@
self._check_compiler()
src, out = self._preprocess(body, headers, include_dirs, lang)
- if isinstance(pattern, str):
+ if isinstance(pattern, basestring):
pattern = re.compile(pattern)
- file = open(out)
- match = 0
- while 1:
- line = file.readline()
- if line == '':
- break
- if pattern.search(line):
- match = 1
- break
+ with open(out) as file:
+ match = False
+ while True:
+ line = file.readline()
+ if line == '':
+ break
+ if pattern.search(line):
+ match = True
+ break
- file.close()
self._clean()
return match
@@ -228,9 +227,9 @@
self._check_compiler()
try:
self._compile(body, headers, include_dirs, lang)
- ok = 1
+ ok = True
except CompileError:
- ok = 0
+ ok = False
logger.info(ok and "success!" or "failure.")
self._clean()
@@ -247,9 +246,9 @@
try:
self._link(body, headers, include_dirs,
libraries, library_dirs, lang)
- ok = 1
+ ok = True
except (CompileError, LinkError):
- ok = 0
+ ok = False
logger.info(ok and "success!" or "failure.")
self._clean()
@@ -267,9 +266,9 @@
src, obj, exe = self._link(body, headers, include_dirs,
libraries, library_dirs, lang)
self.spawn([exe])
- ok = 1
- except (CompileError, LinkError, DistutilsExecError):
- ok = 0
+ ok = True
+ except (CompileError, LinkError, PackagingExecError):
+ ok = False
logger.info(ok and "success!" or "failure.")
self._clean()
@@ -281,7 +280,7 @@
# when implementing a real-world config command!)
def check_func(self, func, headers=None, include_dirs=None,
- libraries=None, library_dirs=None, decl=0, call=0):
+ libraries=None, library_dirs=None, decl=False, call=False):
"""Determine if function 'func' is available by constructing a
source file that refers to 'func', and compiles and links it.
@@ -312,8 +311,6 @@
return self.try_link(body, headers, include_dirs,
libraries, library_dirs)
- # check_func ()
-
def check_lib(self, library, library_dirs=None, headers=None,
include_dirs=None, other_libraries=[]):
"""Determine if 'library' is available to be linked against,
@@ -348,8 +345,5 @@
logger.info(filename)
else:
logger.info(head)
- file = open(filename)
- try:
+ with open(filename) as file:
logger.info(file.read())
- finally:
- file.close()
diff --git a/distutils2/command/install_data.py b/distutils2/command/install_data.py
--- a/distutils2/command/install_data.py
+++ b/distutils2/command/install_data.py
@@ -1,20 +1,18 @@
-"""distutils.command.install_data
+"""Install platform-independent data files."""
-Implements the Distutils 'install_data' command, for installing
-platform-independent data files."""
+# Contributed by Bastian Kleineidam
-# contributed by Bastian Kleineidam
+import os, sys
+from shutil import Error
+from sysconfig import get_paths, format_value
+from distutils2 import logger
+from distutils2.util import convert_path
+from distutils2.command.cmd import Command
-import os
-from distutils2.command.cmd import Command
-from distutils2.util import change_root, convert_path
-from distutils2._backport.sysconfig import get_paths, format_value
-from distutils2._backport.shutil import Error
-
class install_data(Command):
- description = "install data files"
+ description = "install platform-independent data files"
user_options = [
('install-dir=', 'd',
@@ -32,9 +30,9 @@
self.outfiles = []
self.data_files_out = []
self.root = None
- self.force = 0
+ self.force = False
self.data_files = self.distribution.data_files
- self.warn_dir = 1
+ self.warn_dir = True
def finalize_options(self):
self.set_undefined_options('install_dist',
@@ -43,19 +41,20 @@
def run(self):
self.mkpath(self.install_dir)
- for file in self.data_files.items():
- destination = convert_path(self.expand_categories(file[1]))
+ for _file in self.data_files.items():
+ destination = convert_path(self.expand_categories(_file[1]))
dir_dest = os.path.abspath(os.path.dirname(destination))
-
+
self.mkpath(dir_dest)
try:
- (out, _) = self.copy_file(file[0], dir_dest)
- except Error, e:
- self.warn(e)
+ out = self.copy_file(_file[0], dir_dest)[0]
+ except Error:
+ e = sys.exc_info()[1]
+ logger.warning('%s: %s', self.get_command_name(), e)
out = destination
self.outfiles.append(out)
- self.data_files_out.append((file[0], destination))
+ self.data_files_out.append((_file[0], destination))
def expand_categories(self, path_with_categories):
local_vars = get_paths()
@@ -63,15 +62,16 @@
expanded_path = format_value(path_with_categories, local_vars)
expanded_path = format_value(expanded_path, local_vars)
if '{' in expanded_path and '}' in expanded_path:
- self.warn("Unable to expand %s, some categories may missing." %
- path_with_categories)
+ logger.warning(
+ '%s: unable to expand %s, some categories may be missing',
+ self.get_command_name(), path_with_categories)
return expanded_path
def get_source_files(self):
- return self.data_files.keys()
+ return list(self.data_files)
def get_inputs(self):
- return self.data_files.keys()
+ return list(self.data_files)
def get_outputs(self):
return self.outfiles
diff --git a/distutils2/command/install_dist.py b/distutils2/command/install_dist.py
--- a/distutils2/command/install_dist.py
+++ b/distutils2/command/install_dist.py
@@ -1,27 +1,20 @@
-"""distutils.command.install
-
-Implements the Distutils 'install_dist' command."""
-
+"""Main install command, which calls the other install_* commands."""
import sys
import os
-from distutils2._backport import sysconfig
-from distutils2._backport.sysconfig import (get_config_vars, get_paths,
- get_path, get_config_var)
+import sysconfig
+from sysconfig import get_config_vars, get_paths, get_path, get_config_var
from distutils2 import logger
from distutils2.command.cmd import Command
-from distutils2.errors import DistutilsPlatformError
+from distutils2.errors import PackagingPlatformError
from distutils2.util import write_file
from distutils2.util import convert_path, change_root, get_platform
-from distutils2.errors import DistutilsOptionError
+from distutils2.errors import PackagingOptionError
-# compatibility with 2.4 and 2.5
-if sys.version < '2.6':
- HAS_USER_SITE = False
-else:
- HAS_USER_SITE = True
+
+HAS_USER_SITE = True
class install_dist(Command):
@@ -123,7 +116,7 @@
self.exec_prefix = None
self.home = None
if HAS_USER_SITE:
- self.user = 0
+ self.user = False
# These select only the installation base; it's up to the user to
# specify the installation scheme (currently, that means supplying
@@ -158,7 +151,7 @@
# 'install_path_file' is always true unless some outsider meddles
# with it.
self.extra_path = None
- self.install_path_file = 1
+ self.install_path_file = True
# 'force' forces installation, even if target files are not
# out-of-date. 'skip_build' skips running the "build" command,
@@ -166,9 +159,9 @@
# a user option, it's just there so the bdist_* commands can turn
# it off) determines whether we warn about installing to a
# directory not in sys.path.
- self.force = 0
- self.skip_build = 0
- self.warn_dir = 1
+ self.force = False
+ self.skip_build = False
+ self.warn_dir = True
# These are only here as a conduit from the 'build' command to the
# 'install_*' commands that do the real work. ('build_base' isn't
@@ -218,25 +211,27 @@
if ((self.prefix or self.exec_prefix or self.home) and
(self.install_base or self.install_platbase)):
- raise DistutilsOptionError(
+ raise PackagingOptionError(
"must supply either prefix/exec-prefix/home or "
"install-base/install-platbase -- not both")
if self.home and (self.prefix or self.exec_prefix):
- raise DistutilsOptionError(
+ raise PackagingOptionError(
"must supply either home or prefix/exec-prefix -- not both")
if HAS_USER_SITE and self.user and (
self.prefix or self.exec_prefix or self.home or
self.install_base or self.install_platbase):
- raise DistutilsOptionError(
+ raise PackagingOptionError(
"can't combine user with prefix/exec_prefix/home or "
"install_base/install_platbase")
# Next, stuff that's wrong (or dubious) only on certain platforms.
if os.name != "posix":
if self.exec_prefix:
- self.warn("exec-prefix option ignored on this platform")
+ logger.warning(
+ '%s: exec-prefix option ignored on this platform',
+ self.get_command_name())
self.exec_prefix = None
# Now the interesting logic -- so interesting that we farm it out
@@ -355,14 +350,14 @@
self.install_headers is None or
self.install_scripts is None or
self.install_data is None):
- raise DistutilsOptionError(
+ raise PackagingOptionError(
"install-base or install-platbase supplied, but "
"installation scheme is incomplete")
return
if HAS_USER_SITE and self.user:
if self.install_userbase is None:
- raise DistutilsPlatformError(
+ raise PackagingPlatformError(
"user base directory is not specified")
self.install_base = self.install_platbase = self.install_userbase
self.select_scheme("posix_user")
@@ -372,7 +367,7 @@
else:
if self.prefix is None:
if self.exec_prefix is not None:
- raise DistutilsOptionError(
+ raise PackagingOptionError(
"must not supply exec-prefix without prefix")
self.prefix = os.path.normpath(sys.prefix)
@@ -390,7 +385,7 @@
"""Finalize options for non-posix platforms"""
if HAS_USER_SITE and self.user:
if self.install_userbase is None:
- raise DistutilsPlatformError(
+ raise PackagingPlatformError(
"user base directory is not specified")
self.install_base = self.install_platbase = self.install_userbase
self.select_scheme(os.name + "_user")
@@ -405,7 +400,7 @@
try:
self.select_scheme(os.name)
except KeyError:
- raise DistutilsPlatformError(
+ raise PackagingPlatformError(
"no support for installation on '%s'" % os.name)
def dump_dirs(self, msg):
@@ -428,7 +423,7 @@
"""Set the install directories by applying the install schemes."""
# it's the caller's problem if they supply a bad name!
scheme = get_paths(name, expand=False)
- for key, value in scheme.iteritems():
+ for key, value in scheme.items():
if key == 'platinclude':
key = 'headers'
value = os.path.join(value, self.distribution.metadata['Name'])
@@ -469,7 +464,7 @@
self.extra_path = self.distribution.extra_path
if self.extra_path is not None:
- if isinstance(self.extra_path, str):
+ if isinstance(self.extra_path, basestring):
self.extra_path = self.extra_path.split(',')
if len(self.extra_path) == 1:
@@ -477,7 +472,7 @@
elif len(self.extra_path) == 2:
path_file, extra_dirs = self.extra_path
else:
- raise DistutilsOptionError(
+ raise PackagingOptionError(
"'extra_path' option must be a list, tuple, or "
"comma-separated string with 1 or 2 elements")
@@ -504,9 +499,9 @@
if HAS_USER_SITE and not self.user:
return
home = convert_path(os.path.expanduser("~"))
- for name, path in self.config_vars.iteritems():
+ for name, path in self.config_vars.items():
if path.startswith(home) and not os.path.isdir(path):
- os.makedirs(path, 0700)
+ os.makedirs(path, 0o700)
# -- Command execution methods -------------------------------------
@@ -521,7 +516,7 @@
# internally, and not to sys.path, so we don't check the platform
# matches what we are running.
if self.warn_dir and build_plat != get_platform():
- raise DistutilsPlatformError("Can't install when "
+ raise PackagingPlatformError("Can't install when "
"cross-compiling")
# Run all sub-commands (at least those that need to be run)
@@ -536,16 +531,16 @@
outputs = self.get_outputs()
if self.root: # strip any package prefix
root_len = len(self.root)
- for counter in xrange(len(outputs)):
+ for counter in range(len(outputs)):
outputs[counter] = outputs[counter][root_len:]
self.execute(write_file,
(self.record, outputs),
"writing list of installed files to '%s'" %
self.record)
- sys_path = map(os.path.normpath, sys.path)
- sys_path = map(os.path.normcase, sys_path)
- install_lib = os.path.normcase(os.path.normpath(self.install_lib))
+ normpath, normcase = os.path.normpath, os.path.normcase
+ sys_path = [normcase(normpath(p)) for p in sys.path]
+ install_lib = normcase(normpath(self.install_lib))
if (self.warn_dir and
not (self.path_file and self.install_path_file) and
install_lib not in sys_path):
@@ -563,7 +558,8 @@
(filename, [self.extra_dirs]),
"creating %s" % filename)
else:
- self.warn("path file '%s' not created" % filename)
+ logger.warning('%s: path file %r not created',
+ self.get_command_name(), filename)
# -- Reporting methods ---------------------------------------------
diff --git a/distutils2/command/install_distinfo.py b/distutils2/command/install_distinfo.py
--- a/distutils2/command/install_distinfo.py
+++ b/distutils2/command/install_distinfo.py
@@ -1,27 +1,16 @@
-"""
-distutils.command.install_distinfo
-==================================
+"""Create the PEP 376-compliant .dist-info directory."""
-:Author: Josip Djolonga
+# Forked from the former install_egg_info command by Josip Djolonga
-This module implements the ``install_distinfo`` command that creates the
-``.dist-info`` directory for the distribution, as specified in :pep:`376`.
-Usually, you do not have to call this command directly, it gets called
-automatically by the ``install_dist`` command.
-"""
-
-# This file was created from the code for the former command install_egg_info
-
+import codecs
import csv
-from distutils2 import logger
-from distutils2._backport.shutil import rmtree
-from distutils2.command.cmd import Command
import os
import re
-try:
- import hashlib
-except ImportError:
- from distutils2._backport import hashlib
+import hashlib
+
+from distutils2.command.cmd import Command
+from distutils2 import logger
+from shutil import rmtree
class install_distinfo(Command):
@@ -72,13 +61,11 @@
if self.no_resources is None:
self.no_resources = False
-
metadata = self.distribution.metadata
basename = "%s-%s.dist-info" % (
- to_filename(safe_name(metadata['Name'])),
- to_filename(safe_version(metadata['Version'])),
- )
+ to_filename(safe_name(metadata['Name'])),
+ to_filename(safe_version(metadata['Version'])))
self.distinfo_dir = os.path.join(self.distinfo_dir, basename)
self.outputs = []
@@ -105,18 +92,14 @@
installer_path = os.path.join(self.distinfo_dir, 'INSTALLER')
logger.info('creating %s', installer_path)
- f = open(installer_path, 'w')
- try:
+ with open(installer_path, 'w') as f:
f.write(self.installer)
- finally:
- f.close()
self.outputs.append(installer_path)
if self.requested:
requested_path = os.path.join(self.distinfo_dir, 'REQUESTED')
logger.info('creating %s', requested_path)
- f = open(requested_path, 'w')
- f.close()
+ open(requested_path, 'wb').close()
self.outputs.append(requested_path)
@@ -126,25 +109,21 @@
resources_path = os.path.join(self.distinfo_dir,
'RESOURCES')
logger.info('creating %s', resources_path)
- f = open(resources_path, 'wb')
- try:
+ with open(resources_path, 'wb') as f:
writer = csv.writer(f, delimiter=',',
- lineterminator=os.linesep,
+ lineterminator='\n',
quotechar='"')
for tuple in install_data.get_resources_out():
writer.writerow(tuple)
self.outputs.append(resources_path)
- finally:
- f.close()
if not self.no_record:
record_path = os.path.join(self.distinfo_dir, 'RECORD')
logger.info('creating %s', record_path)
- f = open(record_path, 'wb')
- try:
+ with codecs.open(record_path, 'w', encoding='utf-8') as f:
writer = csv.writer(f, delimiter=',',
- lineterminator=os.linesep,
+ lineterminator='\n',
quotechar='"')
install = self.get_finalized_command('install_dist')
@@ -155,18 +134,15 @@
writer.writerow((fpath, '', ''))
else:
size = os.path.getsize(fpath)
- fd = open(fpath, 'r')
- hash = hashlib.md5()
- hash.update(fd.read())
+ with open(fpath, 'rb') as fp:
+ hash = hashlib.md5()
+ hash.update(fp.read())
md5sum = hash.hexdigest()
writer.writerow((fpath, md5sum, size))
# add the RECORD file itself
writer.writerow((record_path, '', ''))
self.outputs.append(record_path)
- finally:
- f.close()
-
def get_outputs(self):
return self.outputs
diff --git a/distutils2/command/install_headers.py b/distutils2/command/install_headers.py
--- a/distutils2/command/install_headers.py
+++ b/distutils2/command/install_headers.py
@@ -1,8 +1,4 @@
-"""distutils.command.install_headers
-
-Implements the Distutils 'install_headers' command, to install C/C++ header
-files to the Python include directory."""
-
+"""Install C/C++ header files to the Python include directory."""
from distutils2.command.cmd import Command
@@ -22,7 +18,7 @@
def initialize_options(self):
self.install_dir = None
- self.force = 0
+ self.force = False
self.outfiles = []
def finalize_options(self):
@@ -37,7 +33,7 @@
self.mkpath(self.install_dir)
for header in headers:
- (out, _) = self.copy_file(header, self.install_dir)
+ out = self.copy_file(header, self.install_dir)[0]
self.outfiles.append(out)
def get_inputs(self):
@@ -45,5 +41,3 @@
def get_outputs(self):
return self.outfiles
-
-# class install_headers
diff --git a/distutils2/command/install_lib.py b/distutils2/command/install_lib.py
--- a/distutils2/command/install_lib.py
+++ b/distutils2/command/install_lib.py
@@ -1,14 +1,12 @@
-"""distutils.command.install_lib
-
-Implements the Distutils 'install_lib' command
-(install all Python modules)."""
-
+"""Install all modules (extensions and pure Python)."""
import os
import sys
+import logging
+from distutils2 import logger
from distutils2.command.cmd import Command
-from distutils2.errors import DistutilsOptionError
+from distutils2.errors import PackagingOptionError
# Extension for Python source files.
@@ -19,7 +17,7 @@
class install_lib(Command):
- description = "install all Python modules (extensions and pure Python)"
+ description = "install all modules (extensions and pure Python)"
# The byte-compilation options are a tad confusing. Here are the
# possible scenarios:
@@ -55,7 +53,7 @@
# let the 'install_dist' command dictate our installation directory
self.install_dir = None
self.build_dir = None
- self.force = 0
+ self.force = False
self.compile = None
self.optimize = None
self.skip_build = None
@@ -70,7 +68,7 @@
'force', 'compile', 'optimize', 'skip_build')
if self.compile is None:
- self.compile = 1
+ self.compile = True
if self.optimize is None:
self.optimize = 0
@@ -80,7 +78,7 @@
if self.optimize not in (0, 1, 2):
raise AssertionError
except (ValueError, AssertionError):
- raise DistutilsOptionError, "optimize must be 0, 1, or 2"
+ raise PackagingOptionError("optimize must be 0, 1, or 2")
def run(self):
# Make sure we have built everything we need first
@@ -109,14 +107,19 @@
if os.path.isdir(self.build_dir):
outfiles = self.copy_tree(self.build_dir, self.install_dir)
else:
- self.warn("'%s' does not exist -- no Python modules to install" %
- self.build_dir)
+ logger.warning(
+ '%s: %r does not exist -- no Python modules to install',
+ self.get_command_name(), self.build_dir)
return
return outfiles
def byte_compile(self, files):
- if hasattr(sys, 'dont_write_bytecode') and sys.dont_write_bytecode:
- self.warn('byte-compiling is disabled, skipping.')
+ if getattr(sys, 'dont_write_bytecode'):
+ # XXX do we want this? because a Python runs without bytecode
+ # doesn't mean that the *dists should not contain bytecode
+ #--or does it?
+ logger.warning('%s: byte-compiling is disabled, skipping.',
+ self.get_command_name())
return
from distutils2.util import byte_compile
@@ -127,6 +130,10 @@
# should at least generate usable bytecode in RPM distributions.
install_root = self.get_finalized_command('install_dist').root
+ # Temporary kludge until we remove the verbose arguments and use
+ # logging everywhere
+ verbose = logger.getEffectiveLevel() >= logging.DEBUG
+
if self.compile:
byte_compile(files, optimize=0,
force=self.force, prefix=install_root,
@@ -134,7 +141,8 @@
if self.optimize > 0:
byte_compile(files, optimize=self.optimize,
force=self.force, prefix=install_root,
- verbose=self.verbose, dry_run=self.dry_run)
+ verbose=verbose,
+ dry_run=self.dry_run)
# -- Utility methods -----------------------------------------------
diff --git a/distutils2/command/install_scripts.py b/distutils2/command/install_scripts.py
--- a/distutils2/command/install_scripts.py
+++ b/distutils2/command/install_scripts.py
@@ -1,17 +1,12 @@
-"""distutils.command.install_scripts
+"""Install scripts."""
-Implements the Distutils 'install_scripts' command, for installing
-Python scripts."""
-
-# contributed by Bastian Kleineidam
-
+# Contributed by Bastian Kleineidam
import os
from distutils2.command.cmd import Command
from distutils2 import logger
-from stat import ST_MODE
-class install_scripts (Command):
+class install_scripts(Command):
description = "install scripts (Python or otherwise)"
@@ -25,19 +20,19 @@
boolean_options = ['force', 'skip-build']
- def initialize_options (self):
+ def initialize_options(self):
self.install_dir = None
- self.force = 0
+ self.force = False
self.build_dir = None
self.skip_build = None
- def finalize_options (self):
+ def finalize_options(self):
self.set_undefined_options('build', ('build_scripts', 'build_dir'))
self.set_undefined_options('install_dist',
('install_scripts', 'install_dir'),
'force', 'skip_build')
- def run (self):
+ def run(self):
if not self.skip_build:
self.run_command('build_scripts')
@@ -53,11 +48,11 @@
if self.dry_run:
logger.info("changing mode of %s", file)
else:
- mode = ((os.stat(file)[ST_MODE]) | 0555) & 07777
+ mode = (os.stat(file).st_mode | 0o555) & 0o7777
logger.info("changing mode of %s to %o", file, mode)
os.chmod(file, mode)
- def get_inputs (self):
+ def get_inputs(self):
return self.distribution.scripts or []
def get_outputs(self):
diff --git a/distutils2/command/register.py b/distutils2/command/register.py
--- a/distutils2/command/register.py
+++ b/distutils2/command/register.py
@@ -1,26 +1,20 @@
-"""distutils.command.register
+"""Register a release with a project index."""
-Implements the Distutils 'register' command (register with the repository).
-"""
+# Contributed by Richard Jones
-# created 2002/10/21, Richard Jones
-
-
-import urllib2
+import sys
import getpass
import urlparse
-import StringIO
-import logging
+import urllib2
+from distutils2 import logger
+from distutils2.util import (read_pypirc, generate_pypirc, DEFAULT_REPOSITORY,
+ DEFAULT_REALM, get_pypirc_path, encode_multipart)
from distutils2.command.cmd import Command
-from distutils2 import logger
-from distutils2.metadata import metadata_to_dict
-from distutils2.util import (read_pypirc, generate_pypirc, DEFAULT_REPOSITORY,
- DEFAULT_REALM, get_pypirc_path)
class register(Command):
- description = "register the distribution with the Python package index"
+ description = "register a release with PyPI"
user_options = [
('repository=', 'r',
"repository URL [default: %s]" % DEFAULT_REPOSITORY),
@@ -37,9 +31,9 @@
def initialize_options(self):
self.repository = None
self.realm = None
- self.show_response = 0
- self.list_classifiers = 0
- self.strict = 0
+ self.show_response = False
+ self.list_classifiers = False
+ self.strict = False
def finalize_options(self):
if self.repository is None:
@@ -48,14 +42,17 @@
self.realm = DEFAULT_REALM
def run(self):
- self.finalize_options()
self._set_config()
# Check the package metadata
check = self.distribution.get_command_obj('check')
- check.strict = self.strict
- check.all = 1
- self.run_command('check')
+ if check.strict != self.strict and not check.all:
+ # If check was already run but with different options,
+ # re-run it
+ check.strict = self.strict
+ check.all = True
+ self.distribution.have_run.pop('check', None)
+ self.run_command('check')
if self.dry_run:
self.verify_metadata()
@@ -123,6 +120,9 @@
3. set the password to a random string and email the user.
'''
+ # TODO factor registration out into another method
+ # TODO use print to print, not logging
+
# see if we can short-cut and get the username/password from the
# config
if self.has_config:
@@ -136,24 +136,24 @@
# get the user's login info
choices = '1 2 3 4'.split()
while choice not in choices:
- self.announce('''\
+ logger.info('''\
We need to know who you are, so please choose either:
1. use your existing login,
2. register as a new user,
3. have the server generate a new password for you (and email it to you), or
4. quit
-Your selection [default 1]: ''', logging.INFO)
+Your selection [default 1]: ''')
- choice = raw_input()
+ choice = input()
if not choice:
choice = '1'
elif choice not in choices:
- print 'Please choose one of the four options!'
+ print('Please choose one of the four options!')
if choice == '1':
# get the username and password
while not username:
- username = raw_input('Username: ')
+ username = input('Username: ')
while not password:
password = getpass.getpass('Password: ')
@@ -164,8 +164,7 @@
# send the info to the server and report the result
code, result = self.post_to_server(self.build_post_data('submit'),
auth)
- self.announce('Server response (%s): %s' % (code, result),
- logging.INFO)
+ logger.info('Server response (%s): %s', code, result)
# possibly save the login
if code == 200:
@@ -174,14 +173,13 @@
# so the upload command can reuse it
self.distribution.password = password
else:
- self.announce(('I can store your PyPI login so future '
- 'submissions will be faster.'),
- logging.INFO)
- self.announce('(the login will be stored in %s)' % \
- get_pypirc_path(), logging.INFO)
+ logger.info(
+ 'I can store your PyPI login so future submissions '
+ 'will be faster.\n(the login will be stored in %s)',
+ get_pypirc_path())
choice = 'X'
while choice.lower() not in 'yn':
- choice = raw_input('Save your login (y/N)?')
+ choice = input('Save your login (y/N)?')
if not choice:
choice = 'n'
if choice.lower() == 'y':
@@ -192,7 +190,7 @@
data['name'] = data['password'] = data['email'] = ''
data['confirm'] = None
while not data['name']:
- data['name'] = raw_input('Username: ')
+ data['name'] = input('Username: ')
while data['password'] != data['confirm']:
while not data['password']:
data['password'] = getpass.getpass('Password: ')
@@ -201,9 +199,9 @@
if data['password'] != data['confirm']:
data['password'] = ''
data['confirm'] = None
- print "Password and confirm don't match!"
+ print("Password and confirm don't match!")
while not data['email']:
- data['email'] = raw_input(' EMail: ')
+ data['email'] = input(' EMail: ')
code, result = self.post_to_server(data)
if code != 200:
logger.info('server response (%s): %s', code, result)
@@ -214,14 +212,14 @@
data = {':action': 'password_reset'}
data['email'] = ''
while not data['email']:
- data['email'] = raw_input('Your email address: ')
+ data['email'] = input('Your email address: ')
code, result = self.post_to_server(data)
logger.info('server response (%s): %s', code, result)
def build_post_data(self, action):
# figure the data to send - the metadata plus some additional
# information used by the package server
- data = metadata_to_dict(self.distribution.metadata)
+ data = self.distribution.metadata.todict()
data[':action'] = action
return data
@@ -230,33 +228,13 @@
''' Post a query to the server, and return a string response.
'''
if 'name' in data:
- self.announce('Registering %s to %s' % (data['name'],
- self.repository),
- logging.INFO)
+ logger.info('Registering %s to %s', data['name'], self.repository)
# Build up the MIME payload for the urllib2 POST data
- boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
- sep_boundary = '\n--' + boundary
- end_boundary = sep_boundary + '--'
- body = StringIO.StringIO()
- for key, value in data.iteritems():
- # handle multiple entries for the same name
- if not isinstance(value, (tuple, list)):
- value = [value]
-
- for value in value:
- body.write(sep_boundary)
- body.write('\nContent-Disposition: form-data; name="%s"'%key)
- body.write("\n\n")
- body.write(value)
- if value and value[-1] == '\r':
- body.write('\n') # write an extra newline (lurve Macs)
- body.write(end_boundary)
- body.write("\n")
- body = body.getvalue()
+ content_type, body = encode_multipart(data.items(), [])
# build the Request
headers = {
- 'Content-type': 'multipart/form-data; boundary=%s; charset=utf-8'%boundary,
+ 'Content-type': content_type,
'Content-length': str(len(body))
}
req = urllib2.Request(self.repository, body, headers)
@@ -268,18 +246,19 @@
data = ''
try:
result = opener.open(req)
- except urllib2.HTTPError, e:
+ except urllib2.HTTPError:
+ e = sys.exc_info()[1]
if self.show_response:
data = e.fp.read()
result = e.code, e.msg
- except urllib2.URLError, e:
- result = 500, str(e)
+ except urllib2.URLError:
+ result = 500, str(sys.exc_info()[1])
else:
if self.show_response:
data = result.read()
result = 200, 'OK'
if self.show_response:
dashes = '-' * 75
- self.announce('%s%s%s' % (dashes, data, dashes))
+ logger.info('%s%s%s', dashes, data, dashes)
return result
diff --git a/distutils2/command/sdist.py b/distutils2/command/sdist.py
--- a/distutils2/command/sdist.py
+++ b/distutils2/command/sdist.py
@@ -1,34 +1,27 @@
-"""distutils.command.sdist
+"""Create a source distribution."""
-Implements the Distutils 'sdist' command (create a source distribution)."""
import os
+import re
import sys
+from StringIO import StringIO
from shutil import rmtree
-import re
-from StringIO import StringIO
-try:
- from shutil import get_archive_formats
-except ImportError:
- from distutils2._backport.shutil import get_archive_formats
-
+from distutils2 import logger
+from distutils2.util import resolve_name, get_archive_formats
+from distutils2.errors import (PackagingPlatformError, PackagingOptionError,
+ PackagingModuleError, PackagingFileError)
from distutils2.command import get_command_names
from distutils2.command.cmd import Command
-from distutils2.errors import (DistutilsPlatformError, DistutilsOptionError,
- DistutilsModuleError, DistutilsFileError)
from distutils2.manifest import Manifest
-from distutils2 import logger
-from distutils2.util import resolve_name
+
def show_formats():
"""Print all possible values for the 'formats' option (used by
the "--help-formats" command-line option).
"""
from distutils2.fancy_getopt import FancyGetopt
- formats = []
- for name, desc in get_archive_formats():
- formats.append(("formats=" + name, None, desc))
- formats.sort()
+ formats = sorted(('formats=' + name, None, desc)
+ for name, desc in get_archive_formats())
FancyGetopt(formats).print_help(
"List of available source distribution formats:")
@@ -36,6 +29,7 @@
_COLLAPSE_PATTERN = re.compile('\\\w\n', re.M)
_COMMENTED_LINE = re.compile('^#.*\n$|^\w*\n$', re.M)
+
class sdist(Command):
description = "create a source distribution (tarball, zip file, etc.)"
@@ -84,26 +78,24 @@
]
negative_opt = {'no-defaults': 'use-defaults',
- 'no-prune': 'prune' }
+ 'no-prune': 'prune'}
default_format = {'posix': 'gztar',
- 'nt': 'zip' }
-
+ 'nt': 'zip'}
def initialize_options(self):
self.manifest = None
-
# 'use_defaults': if true, we will include the default file set
# in the manifest
- self.use_defaults = 1
- self.prune = 1
- self.manifest_only = 0
+ self.use_defaults = True
+ self.prune = True
+ self.manifest_only = False
self.formats = None
- self.keep_temp = 0
+ self.keep_temp = False
self.dist_dir = None
self.archive_files = None
- self.metadata_check = 1
+ self.metadata_check = True
self.owner = None
self.group = None
self.filelist = None
@@ -125,14 +117,13 @@
try:
self.formats = [self.default_format[os.name]]
except KeyError:
- raise DistutilsPlatformError, \
- "don't know how to create source distributions " + \
- "on platform %s" % os.name
+ raise PackagingPlatformError("don't know how to create source "
+ "distributions on platform %s" % os.name)
bad_format = self._check_archive_formats(self.formats)
if bad_format:
- raise DistutilsOptionError, \
- "unknown archive format '%s'" % bad_format
+ raise PackagingOptionError("unknown archive format '%s'" \
+ % bad_format)
if self.dist_dir is None:
self.dist_dir = "dist"
@@ -143,7 +134,7 @@
if self.manifest_builders is None:
self.manifest_builders = []
else:
- if isinstance(self.manifest_builders, str):
+ if isinstance(self.manifest_builders, basestring):
self.manifest_builders = self.manifest_builders.split(',')
builders = []
for builder in self.manifest_builders:
@@ -152,14 +143,13 @@
continue
try:
builder = resolve_name(builder)
- except ImportError, e:
- raise DistutilsModuleError(e)
+ except ImportError:
+ raise PackagingModuleError(sys.exc_info()[1])
builders.append(builder)
self.manifest_builders = builders
-
def run(self):
# 'filelist' contains the list of files that will make up the
# manifest
@@ -191,7 +181,8 @@
"""
template_exists = len(self.distribution.extra_files) > 0
if not template_exists:
- self.warn('Using default file list')
+ logger.warning('%s: using default file list',
+ self.get_command_name())
self.filelist.findall()
if self.use_defaults:
@@ -210,20 +201,24 @@
self.filelist.write(self.manifest)
def add_defaults(self):
- """Add all the default files to self.filelist:
- - all pure Python modules mentioned in setup script
- - all files pointed by package_data (build_py)
- - all files defined in data_files.
- - all files defined as scripts.
- - all C sources listed as part of extensions or C libraries
- in the setup script (doesn't catch C headers!)
- Warns if (README or README.txt) or setup.py are missing; everything
- else is optional.
+ """Add all default files to self.filelist.
+
+ In addition to the setup.cfg file, this will include all files returned
+ by the get_source_files of every registered command. This will find
+ Python modules and packages, data files listed in package_data_,
+ data_files and extra_files, scripts, C sources of extension modules or
+ C libraries (headers are missing).
"""
+ if os.path.exists('setup.cfg'):
+ self.filelist.append('setup.cfg')
+ else:
+ logger.warning("%s: standard 'setup.cfg' file not found",
+ self.get_command_name())
+
for cmd_name in get_command_names():
try:
cmd_obj = self.get_finalized_command(cmd_name)
- except DistutilsOptionError:
+ except PackagingOptionError:
pass
else:
self.filelist.extend(cmd_obj.get_source_files())
@@ -252,8 +247,7 @@
vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr',
'_darcs']
vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps)
- self.filelist.exclude_pattern(vcs_ptrn, is_regex=1)
-
+ self.filelist.exclude_pattern(vcs_ptrn, is_regex=True)
def make_release_tree(self, base_dir, files):
"""Create the directory tree that will become the source
@@ -285,18 +279,19 @@
msg = "copying files to %s..." % base_dir
if not files:
- logger.warn("no files to distribute -- empty manifest?")
+ logger.warning("no files to distribute -- empty manifest?")
else:
logger.info(msg)
for file in self.distribution.metadata.requires_files:
if file not in files:
- msg = "'%s' must be included explicitly in 'extra_files'" % file
- raise DistutilsFileError(msg)
+ msg = "'%s' must be included explicitly in 'extra_files'" \
+ % file
+ raise PackagingFileError(msg)
for file in files:
if not os.path.isfile(file):
- logger.warn("'%s' not a regular file -- skipping", file)
+ logger.warning("'%s' not a regular file -- skipping", file)
else:
dest = os.path.join(base_dir, file)
self.copy_file(file, dest, link=link)
@@ -342,12 +337,12 @@
"""
return self.archive_files
- def create_tree(self, base_dir, files, mode=0777, verbose=1, dry_run=0):
- need_dir = {}
+ def create_tree(self, base_dir, files, mode=0o777, verbose=1,
+ dry_run=False):
+ need_dir = set()
for file in files:
- need_dir[os.path.join(base_dir, os.path.dirname(file))] = 1
- need_dirs = sorted(need_dir)
+ need_dir.add(os.path.join(base_dir, os.path.dirname(file)))
# Now create them
- for dir in need_dirs:
+ for dir in sorted(need_dir):
self.mkpath(dir, mode, verbose=verbose, dry_run=dry_run)
diff --git a/distutils2/command/test.py b/distutils2/command/test.py
--- a/distutils2/command/test.py
+++ b/distutils2/command/test.py
@@ -1,20 +1,20 @@
+"""Run the project's test suite."""
+
import os
import sys
+import logging
import unittest
+from distutils2 import logger
from distutils2.command.cmd import Command
-from distutils2.errors import DistutilsOptionError
+from distutils2.database import get_distribution
+from distutils2.errors import PackagingOptionError
from distutils2.util import resolve_name
-try:
- from pkgutil import get_distribution
-except ImportError:
- from distutils2._backport.pkgutil import get_distribution
-
class test(Command):
- description = "run the distribution's test suite"
+ description = "run the project's test suite"
user_options = [
('suite=', 's',
@@ -34,11 +34,11 @@
self.build_lib = self.get_finalized_command("build").build_lib
for requirement in self.tests_require:
if get_distribution(requirement) is None:
- self.announce("test dependency %s is not installed, "
- "tests may fail" % requirement)
+ logger.warning("test dependency %s is not installed, "
+ "tests may fail", requirement)
if (not self.suite and not self.runner and
self.get_ut_with_discovery() is None):
- raise DistutilsOptionError(
+ raise PackagingOptionError(
"no test discovery available, please give a 'suite' or "
"'runner' option or install unittest2")
@@ -60,16 +60,22 @@
self.run_command('build')
sys.path.insert(0, build.build_lib)
+ # Temporary kludge until we remove the verbose arguments and use
+ # logging everywhere
+ logger = logging.getLogger('distutils2')
+ verbose = logger.getEffectiveLevel() >= logging.DEBUG
+ verbosity = verbose + 1
+
# run the tests
if self.runner:
resolve_name(self.runner)()
elif self.suite:
- runner = unittest.TextTestRunner(verbosity=self.verbose + 1)
+ runner = unittest.TextTestRunner(verbosity=verbosity)
runner.run(resolve_name(self.suite)())
elif self.get_ut_with_discovery():
ut = self.get_ut_with_discovery()
test_suite = ut.TestLoader().discover(os.curdir)
- runner = ut.TextTestRunner(verbosity=self.verbose + 1)
+ runner = ut.TextTestRunner(verbosity=verbosity)
runner.run(test_suite)
finally:
sys.path[:] = prev_syspath
diff --git a/distutils2/command/upload.py b/distutils2/command/upload.py
--- a/distutils2/command/upload.py
+++ b/distutils2/command/upload.py
@@ -1,27 +1,21 @@
-"""distutils.command.upload
+"""Upload a distribution to a project index."""
-Implements the Distutils 'upload' subcommand (upload package to PyPI)."""
-import os
+import os, sys
import socket
+import logging
import platform
-import logging
-from urllib2 import urlopen, Request, HTTPError
+import urlparse
+from io import BytesIO
from base64 import standard_b64encode
-import urlparse
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-try:
- from hashlib import md5
-except ImportError:
- from distutils2._backport.hashlib import md5
+from hashlib import md5
+from urllib2 import HTTPError
+from urllib2 import urlopen, Request
-from distutils2.errors import DistutilsOptionError
-from distutils2.util import spawn
+from distutils2 import logger
+from distutils2.errors import PackagingOptionError
+from distutils2.util import (spawn, read_pypirc, DEFAULT_REPOSITORY,
+ DEFAULT_REALM, encode_multipart)
from distutils2.command.cmd import Command
-from distutils2.metadata import metadata_to_dict
-from distutils2.util import read_pypirc, DEFAULT_REPOSITORY, DEFAULT_REALM
class upload(Command):
@@ -46,10 +40,10 @@
def initialize_options(self):
self.repository = None
self.realm = None
- self.show_response = 0
+ self.show_response = False
self.username = ''
self.password = ''
- self.show_response = 0
+ self.show_response = False
self.sign = False
self.identity = None
self.upload_docs = False
@@ -60,9 +54,8 @@
if self.realm is None:
self.realm = DEFAULT_REALM
if self.identity and not self.sign:
- raise DistutilsOptionError(
- "Must use --sign for --identity to have meaning"
- )
+ raise PackagingOptionError(
+ "Must use --sign for --identity to have meaning")
config = read_pypirc(self.repository, self.realm)
if config != {}:
self.username = config['username']
@@ -77,7 +70,8 @@
def run(self):
if not self.distribution.dist_files:
- raise DistutilsOptionError("No dist file created in earlier command")
+ raise PackagingOptionError(
+ "No dist file created in earlier command")
for command, pyversion, filename in self.distribution.dist_files:
self.upload_file(command, pyversion, filename)
if self.upload_docs:
@@ -90,13 +84,13 @@
# XXX to be refactored with register.post_to_server
def upload_file(self, command, pyversion, filename):
# Makes sure the repository URL is compliant
- schema, netloc, url, params, query, fragments = \
+ scheme, netloc, url, params, query, fragments = \
urlparse.urlparse(self.repository)
if params or query or fragments:
raise AssertionError("Incompatible url %s" % self.repository)
- if schema not in ('http', 'https'):
- raise AssertionError("unsupported schema " + schema)
+ if scheme not in ('http', 'https'):
+ raise AssertionError("unsupported scheme " + scheme)
# Sign if requested
if self.sign:
@@ -108,99 +102,69 @@
# Fill in the data - send all the metadata in case we need to
# register a new release
- content = open(filename,'rb').read()
+ with open(filename, 'rb') as f:
+ content = f.read()
- data = metadata_to_dict(self.distribution.metadata)
+ data = self.distribution.metadata.todict()
# extra upload infos
data[':action'] = 'file_upload'
data['protcol_version'] = '1'
- data['content'] = [os.path.basename(filename), content]
+ data['content'] = (os.path.basename(filename), content)
data['filetype'] = command
data['pyversion'] = pyversion
data['md5_digest'] = md5(content).hexdigest()
- comment = ''
if command == 'bdist_dumb':
- comment = 'built for %s' % platform.platform(terse=1)
- data['comment'] = comment
+ data['comment'] = 'built for %s' % platform.platform(terse=True)
if self.sign:
- data['gpg_signature'] = [(os.path.basename(filename) + ".asc",
- open(filename+".asc").read())]
+ with open(filename + '.asc') as fp:
+ sig = fp.read()
+ data['gpg_signature'] = [
+ (os.path.basename(filename) + ".asc", sig)]
# set up the authentication
- auth = "Basic " + standard_b64encode(self.username + ":" +
- self.password)
+ # The exact encoding of the authentication string is debated.
+ # Anyway PyPI only accepts ascii for both username or password.
+ user_pass = (self.username + ":" + self.password).encode('ascii')
+ auth = b"Basic " + standard_b64encode(user_pass)
# Build up the MIME payload for the POST data
- boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
- sep_boundary = '\n--' + boundary
- end_boundary = sep_boundary + '--'
- body = StringIO()
- file_fields = ('content', 'gpg_signature')
+ files = []
+ for key in ('content', 'gpg_signature'):
+ if key in data:
+ filename_, value = data.pop(key)
+ files.append((key, filename_, value))
- for key, values in data.iteritems():
- # handle multiple entries for the same name
- if not isinstance(values, (tuple, list)):
- values = [values]
+ content_type, body = encode_multipart(data.items(), files)
- content_dispo = 'Content-Disposition: form-data; name="%s"' % key
-
- if key in file_fields:
- filename_, content = values
- filename_ = ';filename="%s"' % filename_
- body.write(sep_boundary)
- body.write("\n")
- body.write(content_dispo)
- body.write(filename_)
- body.write("\n\n")
- body.write(content)
- else:
- for value in values:
- body.write(sep_boundary)
- body.write("\n")
- body.write(content_dispo)
- body.write("\n\n")
- body.write(value)
- if value and value[-1] == '\r':
- # write an extra newline (lurve Macs)
- body.write('\n')
-
- body.write(end_boundary)
- body.write("\n")
- body = body.getvalue()
-
- self.announce("Submitting %s to %s" % (filename, self.repository),
- logging.INFO)
+ logger.info("Submitting %s to %s", filename, self.repository)
# build the Request
- headers = {'Content-type':
- 'multipart/form-data; boundary=%s' % boundary,
+ headers = {'Content-type': content_type,
'Content-length': str(len(body)),
'Authorization': auth}
- request = Request(self.repository, data=body,
- headers=headers)
+ request = Request(self.repository, body, headers)
# send the data
try:
result = urlopen(request)
status = result.code
reason = result.msg
- except socket.error, e:
- self.announce(str(e), logging.ERROR)
+ except socket.error:
+ logger.error(sys.exc_info()[1])
return
- except HTTPError, e:
+ except HTTPError:
+ e = sys.exc_info()[1]
status = e.code
reason = e.msg
if status == 200:
- self.announce('Server response (%s): %s' % (status, reason),
- logging.INFO)
+ logger.info('Server response (%s): %s', status, reason)
else:
- self.announce('Upload failed (%s): %s' % (status, reason),
- logging.ERROR)
+ logger.error('Upload failed (%s): %s', status, reason)
- if self.show_response:
- msg = '\n'.join(('-' * 75, result.read(), '-' * 75))
- self.announce(msg, logging.INFO)
+ if self.show_response and logger.isEnabledFor(logging.INFO):
+ sep = '-' * 75
+ logger.info('%s\n%s\n%s', sep, result.read().decode(), sep)
diff --git a/distutils2/command/upload_docs.py b/distutils2/command/upload_docs.py
--- a/distutils2/command/upload_docs.py
+++ b/distutils2/command/upload_docs.py
@@ -1,65 +1,38 @@
-import os
+"""Upload HTML documentation to a project index."""
+
+import os, sys
import base64
-import httplib
import socket
-import urlparse
import zipfile
import logging
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
+import httplib
+import urlparse
+from io import BytesIO
from distutils2 import logger
-from distutils2.command.upload import upload
+from distutils2.util import (read_pypirc, DEFAULT_REPOSITORY, DEFAULT_REALM,
+ encode_multipart)
+from distutils2.errors import PackagingFileError
from distutils2.command.cmd import Command
-from distutils2.errors import DistutilsFileError
-from distutils2.util import read_pypirc, DEFAULT_REPOSITORY, DEFAULT_REALM
+
def zip_dir(directory):
- """Compresses recursively contents of directory into a StringIO object"""
- destination = StringIO()
- zip_file = zipfile.ZipFile(destination, "w")
- for root, dirs, files in os.walk(directory):
- for name in files:
- full = os.path.join(root, name)
- relative = root[len(directory):].lstrip(os.path.sep)
- dest = os.path.join(relative, name)
- zip_file.write(full, dest)
- zip_file.close()
+ """Compresses recursively contents of directory into a BytesIO object"""
+ destination = BytesIO()
+ with zipfile.ZipFile(destination, "w") as zip_file:
+ for root, dirs, files in os.walk(directory):
+ for name in files:
+ full = os.path.join(root, name)
+ relative = root[len(directory):].lstrip(os.path.sep)
+ dest = os.path.join(relative, name)
+ zip_file.write(full, dest)
return destination
-# grabbed from
-# http://code.activestate.com/recipes/146306-http-client-to-post-using-multip…
-def encode_multipart(fields, files, boundary=None):
- """
- fields is a sequence of (name, value) elements for regular form fields.
- files is a sequence of (name, filename, value) elements for data to be uploaded as files
- Return (content_type, body) ready for httplib.HTTP instance
- """
- if boundary is None:
- boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
- l = []
- for (key, value) in fields:
- l.extend([
- '--' + boundary,
- 'Content-Disposition: form-data; name="%s"' % key,
- '',
- value])
- for (key, filename, value) in files:
- l.extend([
- '--' + boundary,
- 'Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename),
- '',
- value])
- l.append('--' + boundary + '--')
- l.append('')
- body = '\r\n'.join(l)
- content_type = 'multipart/form-data; boundary=%s' % boundary
- return content_type, body
class upload_docs(Command):
+ description = "upload HTML documentation to PyPI"
+
user_options = [
('repository=', 'r',
"repository URL [default: %s]" % DEFAULT_REPOSITORY),
@@ -72,7 +45,7 @@
def initialize_options(self):
self.repository = None
self.realm = None
- self.show_response = 0
+ self.show_response = False
self.upload_dir = None
self.username = ''
self.password = ''
@@ -87,7 +60,7 @@
self.upload_dir = os.path.join(build.build_base, "docs")
if not os.path.isdir(self.upload_dir):
self.upload_dir = os.path.join(build.build_base, "doc")
- self.announce('Using upload directory %s' % self.upload_dir)
+ logger.info('Using upload directory %s', self.upload_dir)
self.verify_upload_dir(self.upload_dir)
config = read_pypirc(self.repository, self.realm)
if config != {}:
@@ -101,30 +74,31 @@
index_location = os.path.join(upload_dir, "index.html")
if not os.path.exists(index_location):
mesg = "No 'index.html found in docs directory (%s)"
- raise DistutilsFileError(mesg % upload_dir)
+ raise PackagingFileError(mesg % upload_dir)
def run(self):
name = self.distribution.metadata['Name']
version = self.distribution.metadata['Version']
zip_file = zip_dir(self.upload_dir)
- fields = [(':action', 'doc_upload'), ('name', name), ('version', version)]
+ fields = [(':action', 'doc_upload'),
+ ('name', name), ('version', version)]
files = [('content', name, zip_file.getvalue())]
content_type, body = encode_multipart(fields, files)
credentials = self.username + ':' + self.password
- auth = "Basic " + base64.encodestring(credentials).strip()
+ auth = b"Basic " + base64.encodebytes(credentials.encode()).strip()
- self.announce("Submitting documentation to %s" % (self.repository))
+ logger.info("Submitting documentation to %s", self.repository)
- schema, netloc, url, params, query, fragments = \
- urlparse.urlparse(self.repository)
- if schema == "http":
+ scheme, netloc, url, params, query, fragments = urlparse.urlparse(
+ self.repository)
+ if scheme == "http":
conn = httplib.HTTPConnection(netloc)
- elif schema == "https":
+ elif scheme == "https":
conn = httplib.HTTPSConnection(netloc)
else:
- raise AssertionError("unsupported schema "+schema)
+ raise AssertionError("unsupported scheme %r" % scheme)
try:
conn.connect()
@@ -134,23 +108,23 @@
conn.putheader('Authorization', auth)
conn.endheaders()
conn.send(body)
- except socket.error, e:
- self.announce(str(e), logging.ERROR)
+
+ except socket.error:
+ logger.error(sys.exc_info()[1])
return
r = conn.getresponse()
if r.status == 200:
- self.announce('Server response (%s): %s' % (r.status, r.reason))
+ logger.info('Server response (%s): %s', r.status, r.reason)
elif r.status == 301:
location = r.getheader('Location')
if location is None:
location = 'http://packages.python.org/%s/' % name
- self.announce('Upload successful. Visit %s' % location)
+ logger.info('Upload successful. Visit %s', location)
else:
- self.announce('Upload failed (%s): %s' % (r.status, r.reason),
- logging.ERROR)
+ logger.error('Upload failed (%s): %s', r.status, r.reason)
- if self.show_response:
- msg = '\n'.join(('-' * 75, r.read(), '-' * 75))
- self.announce(msg)
+ if self.show_response and logger.isEnabledFor(logging.INFO):
+ sep = '-' * 75
+ logger.info('%s\n%s\n%s', sep, r.read().decode('utf-8'), sep)
diff --git a/distutils2/command/wininst-10.0-amd64.exe b/distutils2/command/wininst-10.0-amd64.exe
new file mode 100644
index 0000000000000000000000000000000000000000..11f98cd2adf1075b7ff7be7f02ebc8e743bb6b9e
GIT binary patch
literal 222208
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�$n<uxY!{R)k#m2I;l>cM+W77*{oyS3lOIen^6SL7
z;b(Yg1X($MxxvQ<ms6OUK66ng^fcHzL#s~h!qcOnqu=9e;n!;8E`5)}Q84NWKzJ19
z(@N;E2%xDm^+@z#Y%HOkmB<T(G&<!+E8soKOyiyQgih0;(|DMae!qk=9cBo=u7K2~
z^r!HM-eb-8q>bmzFr+oh1K~vuZx|fX@OU%@CAJ?7JGA`31k}dIKwjS78=Y0|w35SW
z__f$H;k>H@3;35KceRS;`FLvQ^AwMY<-v7gmdfE@W2T=iE9^31C1z#usl<InUv=j`
zsK_THRR<1iYN3v<nd`}kURh?m(DpLGQAvp2k1H_Sc+~ooo$Uv?05*0tp5p?lyy*mR
zw4^t9NA8xIVzQ-v27p5i%D5UOZfTS)(fCpVFZ!hco{R#4?S<(<fI%V2(tt99WJHU^
z8UTC=X0pByfcB2ufly0LFpa7Z<mt>>n(V@h8x3mL{R*2T`4T3~;m)e#d{`#R;IaqB
z7#L7xRiWN>Dc2)Y*#KuRX9W8i2sgCd=K$)<9@4LShJt>59waQ_p)SRVh&&;N=N7QV
z1u!}D^UkiliAHzw+&4%<l7j0BKwj}!llv@&x22Ig03=hsV)_Zen-9Lt0MrT3wBYUB
zl|bgAtA%qHS_jWvP3x8aiq=I}(fa=l8h>#1e}%?H-%ewbLE~!(3^YTbWg5~=XpLy^
zZaOj`<Un{p%;1Ny14U65T}#yK&i+3ls-F|JE+swTNq8|*P(_p3FR?1g9nGs3pKgMx
z?)tpBS>n?#p>LV{Ym6j|Wj~|S4Qzu31MA&(j%4>t7hJyu;Ha_m;9t<J3UE(j3|@a`
zm|z&~!C#GgBjt0?=z1lXw|Z>W88U|LV%b}rp_mC@{3`?A>yCO&Gl$5g{2{n`U@`_F
zSe1_*#F%34GDk2)oBRnzpS7LMh~#dzIauW6sgV=l#h|`p;|cTyFi1y!0QUh#VV-fH
zZmGw1CDFS(Z*Drx!0eGQ^jps+EZn-3!Q4TBvGgIVEpx!gP03N++4`?WCFeabL@e9O
zUBtI;faaUOK-(KIHv}qg%2%WcvkyO3)R6FcxCV&*Ar2(WpPX0!NKdwSth?TQAZ~R>
z*?yS6ie+&Q<fp}Z!VO{Q08E>_21(3sklhXLsLy>6TMOqa(>GcB@*ebt&{=FgGS3l_
zQg{?5KaQRObvG3*uTnGQ&Kp`L7KG>0+h~@=9{9WyLHMOdiOx`u6+pJ+{!TiOrp4=?
zW0y?d8R9j~Ni{7S0>HH7uCoUxlphbeeW0j-xcK_qewo$eu0zt^`7x39)L|lRi{US*
zxAzO$)!VO=S#79?REh64uzV{!+}>P?T6b`Rdcp&$Pn7pd5v$#@-?CVi!i&HoZ3tPR
zs~+kT1=dh%<we@!c19LuO=3G^e>|n@UA~V5F3c?olQL0LkMlDrCjb{Ng%!|hND3~5
zNcw(-&CkRogT#74c2!~>gdckLb!#yaYgI57f1V!(%<jy@lf`?hkK2Xda5b-2aQHW`
zq`>3QsDumEbO_N>tFS|?UaN(hycy}i6fzRyQMNc8oxe{%rG2ewbruZq&{T~79)s_B
zYsWq^L}oL_D(o<RFwJsEyVx6L@!ezP@ZDMQf%wS17teaJfbc$~eE?6xYqRQRq#|9j
zt73N}kob+s1e*G^dF<w3ZzWuiq2BAk1&_p%)NYye^kyIQ)8>KTR7pK=U3-hTr59Y7
z2UoR&_^-;V57{I=8t-4$5VU!-8{)m`C=1EmAhQNZuU7PGZ{GejJG|lEi9Jclv*L`8
zP@ShRe=P+{zn4iLWY~tf=rOwxa;O_*``TVa^cWruq24w;xPUE^@uZ+0!_k^;R8OQd
zm3Z6-!i!|}f_=>>HpfG|;y2CosMQ@H>!k{lPmfA^Rd|kv7g~E#iOg9~TjSLcg67o8
zI<+foAm(c}xHsjej?M?eYjG;8S4^V1VZAj$q>(b4I|Qk8^d!^^FHV)ibBEOR=uP<T
zvY^&|Esd`yUJo3Yi5CtvH!bAImE=%w(gfeQ4rqJ_6A*RG&rT4o@Cy|5mCwnvg8hBI
zHgXf)OwXcF^B_77=KDFI6-3IP?cp<<FS1GO5Y7TxTI<Cn{TT%RjFS6sPVh!HHxo*D
z*-l(!!fBGlUxW#zb}qEV>*=)pCaJOz&E!zeMB8*?{9f(8p4{d%O#W?T_vbaQnF<;2
z4W{dLS$_9Ne)myd^rCrIr0iFGxpZbBYI4P0r?{g^w8N}K%APi!6d88>*FAii9+aO6
zIstlI{*;>jq2M<dc4|lIeUgWA@G}`fS7sLydAfZ-EW`M0!ES?6e116UN>RNc@`(Nh
zrLqrVsd0%?E;UCC*;s)RzfR3b5A|f5(I9gdD6AB9I_NM<bxkFH(m6rnZr^xZ>5Ac(
zglH1j{`0NG=A9JqD4z6Yx5?~8IPg+{9rcF;@A_F_ofMhlP$Ey%N#a(JBxjZI!q?%^
zR!~^K_C_|CO3wYs%LCbs;9rPaaV6heBAeEG>wNAbq0c0Cz1pBlv<eEI4>aZd2l#JU
zp;Xy&Kv6ZbH}A;W&!oI#poNZk%MXTb^s(M`Ryy8@SzTe1PqkPfi(h!dxgc2T@P$*h
z4{AOF#+0{#x97F&Q`9PGuW=2ucT{3WBnE1B`cYPAIVf)Rd+Xc>#EJ@BVzTI}E<W;U
z`N0i+z4%F;wb)?Z5wZNcbQWYd6az<TJ4P?Xt#Voe4ap*>eF`Hg3Cn4*5GaHzIP9@>
zv~}*J;6QP!8T<IOL<+ADKsAnoPoXPCtu{+}9cuub-S}=W=xjh&<Yqb#B;uS2KN7xp
z84mp<esiUnj#hELF@#<YiQx|jUw124V^qlDV_yOKf8t|@q)PM(eaymkdD-X4hhgvY
zxC01m8!s5vSMb1X`V?y_CibxR8E@XFp`Oyl%L`zrYeh-DWDfO#+XCo!4Y-JPu?KM*
z(7sp=ttCK3X;V}hQ(GasOc+w~OL<jK^h7tcVu&k#{f0gmO!9BC&Z)4v;PI?@k6Np%
z2;VDy;(4&4rME~Fxc4%|oXZR(m6|@0!0tXJv9ILtsy|7VLz4SF$^E4iw*DmarJpT*
zSH?}@QpBf67MYqpj++mhDnAmsklhA)wO3@iKC&({)f_joE6ne*N-Vk^KVGg!OH2#A
zb--fQ&<NlGv8*?~=g02<wZgow6Ob|_c#MWMZsqw4aA#fUCvx5{@#z61nO8j{kNjND
z1Cc0}>GXnl;Cbc+4sQc1;G7ak-RNZ}yzYN0;lT5>n_xy{@{K^(rjI2s$%g<3gn-L{
zuTu3|N#9{P2-$05d^~Ko;VsFQ4nRdJ^S&hGn;iJ+Mw+xyM}mmz34y<{jyXsZQQgNW
zoCJ4fh3jRwM|wJ@S1fti1hWIi_`D&fN~VY9KbnbhI942KEEk3XWCY51N`N8E20hF5
zU=h*@zxU>VF$M=R?=vIofWpDF0LzmZNP}G!OI#+T8Uqq4Ks6u>Qp(tFl_1X5q$$@!
zuNlLxQ=%<qp9u!w-5O4hO}%8i&0@T697w^Pc^N$gdijmc;@P+Z#Z$L$N1>m4+yuJo
zeu1BJR(t@m#^4Dt(T^>PyINs%){-Q?zMnP5=JgQ-P#KM}O7Ly;QQtjef^-R`?;OGO
z4N8AY=?j!TP3fbQewWfEl+K~lL+K<+hfsPQr2?gA4`ccXrH3i4r}P5#C{sF<Qial4
zl=>;1Lumn}cTl>3(nXXOQhHYx_`gGG5vBK1dLN}FlrE=~k2ly%`IHt?I_M~-sgz!!
zoK{Ldru02ZYbmXy^c6~fLFsdpmQuQg()%b~M5#jQ6iUZZI*d{WrDjUMBwYQI()9N*
z?M3OOCQLu0w1v`xl<uIkjnd7O-a+XCN*|(hHKmif<j<ruhnJ_mWa@o9rCE^1Q>Ii0
z(-b|r$y8PqIya>{WK~$SCL7=Nxb|O3i8#DW5J+D}eQJk&&3tjoLEko$@y{o|egLvP
zYlbQ9N5Mi2_VB|*3_HLo4zPNk+H$)u8tW0t<u@sCV+r;tDJj{!Sqb+6&0@!`!C#{D
z(5s@4f*bPcLv1zUaF2k6;J<I<dE8CBHVey#=YhpG&jIy3@Y@W3El?kRH27<Uzm8CI
z`~?76#{Eq-ec%sjK*FoQMKdN;34S$9S~2Me?QbnjbqTQreDmwqj32CXa2ds?eucYd
z5fS&aMN@*ev_(z9-kYqw0C4(pd|T8U6iXOnfOanllR?lHwFc98mX&g>h>mE?7MjN|
zEa>Q1dnxpgP|}Ln0<?teK2}5cfIg@L#Lzj+EaCqW@O8mg2H<NtHt1&ydfB1L3?(eJ
z_z_J^?$wI99fUWj)sHxP4>}tj`yLFvYkQlOjTdkof6d=3Gtd@DxAqd#<z60TV-!t5
z0OPc>F@_~7J%pcB-k)Er>FGzr`<cI&1GCcM*5|;ybj-`ZyiBAB=C@$JhWQ*Yy&iL0
z{X~^ld>b{|K)$hqwiKucB&XE2D+KOU<A=Y^K1==81XA>d1dWJ5iW)O_ArcV?@~lZB
zB?wf|Ngy?5N1PJkOq~Qu;RF)8bn$P}NAA^uBW{J3kwEEwWNpX=#P#RhNOpg?Pv{Vl
zXRUtKc!@r(y1tIn2WU~+l}wyI(Ju7y8~q`EI}pFEn5SVL62YfoUJK?mW8Qv%iztNT
zo9DoMf{YUg$=v7L=(8Q@!{Y^BmQGGpgQTA9klF;$$_Ik|B<8UPiVLjm&3-V2i|hR(
z5BQ2}X0iT&gBcE{*cHocdz)ktgQ?;c$)dKjDnIpG>iyY`KGs0-gILUjdQLr^8NgkR
zxVh$l7S=mo7tOp@q;;Fmf!^T3y$;vNC?d+k;FC0kQ$lC)d$NAE-?vS_LjN`dER8<(
ztRBcdKy!4Pzy;vOK>2}S6EX+Xmhf}xp9H7C556t|ITLtJW@pY&o-+V`ct$GEu)$A!
zlj3e{t4;}y*<`M;_9?^f4(rvxQ^6F-4V{Yjom2(+$Xy^mtsp?FfhWztvi3DMc7lp%
z0?;|M-63u8sa1Bd4EK<hVN+sRIpT_U@@Uy76|O$PM~vGCImA#ZSZri1;w2UsxA~nV
zBqM7wFEPou9hEGQVdV7V1^Vz?v3x2JEIF!g(?5A>vDeH(sE-njI!fB3x*js0t5Kp2
zj#!^Lzz5(Yj-AgBz>{6e1MC2OfHpwyBnI%MgQmo3;@X}9C{Jg_Eu3%ch4iTRX9?>4
zIq;VOs_p`hafJqJ_uL~0Z&KtxT?@>kUHEvo+1*QPw(I2P<5^hRM|gA=7?dz)ZWo@o
zPIGtGitkY>^@lw_#D#4oH|5Eb+LpV~$q8g>3zy-sUt;-V<n;t{w4jl32hYgRerRO4
z=~NX_*?7M6ON+3fNGbMaD3OJ&^gW|MD{j(ly_*+Z3i!<RW{GA0hvE^tw3qmUvKR2a
z*qcN302QOXt=gMLrA(+)H-kR)6I*FAVhg*pUl@;j@-~5YA#*Wh2Kz8?hEc@=js7%l
zC7_CX`zpf~{2xH)V^vCfOo165y<k)5Euz~M%YHB>lobc3g5?^vlvh30Q>~f`Q)p_~
z!pr+3seOFW^EMyDS#vw^Gk(Sieta0*Zt%6oeAl!{%{7K+Tgw|B>!n7g`mnv(R;<w;
z3q*jww&8Am_&3HE?Xdair738-3cm{lKhT%2U4ur=T^fy~-hpqcL{ET`eIPvFUViYg
zUXfYpFyYd}v+cZ!AD9a~kIn``D{Pp^W`Gl#DpdZ^<@=BJnB@Sp%c`T=aLfJ`2UFaQ
zvS~(Ycy>Dc3;~z^kC%1*v396}*;2$Wq<l?1HY1g)LbdX$CwHn<!|P@^Fc=obI;E?C
z6407HyMO%8UB4AZvC!iIl9X5F?)=)^*D?2+J?RQNudM>E&TcbKQt5Aeu@IwL<yDI4
z{m}UI*j4+$J7dQDh@aptlfb_Stv2YsH-*yZm=SaCy`ogUJ^lOtIP}81hkceNrTybB
zlO}p<ppv&=5$7JjNU%i-_d^O9pUvoOzg9}8_xhjIM&lvT;TfPvHsfL`Rj8<>tJ!uX
z&)H|YSqKhbrFb4}xvlB%AXV&I>q>kMewwZNZ{Szu&amSrzCjaJP_eXEu~<*-kHyrs
z9Urw(3+7ONmCBfFXQZ3rDWb!CPJGg&kfh)+mOhp)fDytZhx#Ixyi8{!Mmyi3=HK&J
z*nNMt$B%LdJiS1Rm&F3CRdc{R#8a&-f$(hsa5Asgs+R~toq&=?d)+SxrB8GSu}^4o
z$(dlPq+T|!e;-oUlZU;VZt^p4OSbNdEHg=JC5EX@8`ANZS==tT9XHi!Lsp|+^tQzN
z`dF!l?~UCpZnY|EiycUAmp6@6^qP1T3l&JEm&S!`>Rxla2VA;CJ@_-+z*e!CUiZIj
z;)`I|zxezN4pb-;N@oS_(%ZO=ES^)E_h>M!G_MeI5QO-R(%ZBd8G(0%(y?@?FP5@F
zsSrR=3^sC&Ilwl1=m;M0qNVn)WLA%$dNBqgv3m7{HP}-U-`TCkvf@LejV-u4HaK32
z$gW>w_SEawS-|L0qYYBtp0xwiy>=<@{F)m=*1X0?hNye3faB00Mc?IVtF?!&hdZC>
z{U}?HF98SpvgN#ihKTeQ(BnxcWDO02)D)V^YSk8V(7q8>L+QGnU>=T^@-98%(gxj)
zbh-huv0%`q?$hJfc{bI$%x%qs_4bb>%OxVLFN`gjgXZ>+ke0|afmZc5#5AlfJ~%;r
zPtFq^_|V&Ga8z~`o(}E#<^%HZmA&jL{#Hh=?N__a0v#E|4&bSmX+5@@g}51J|AUul
zbtVVTI?Ex^08)<ELSBRkpPwkf&n8i)&D)Lp7!ySjpNyksU*BRh+c#MW>s-}jqKbC<
zQnG1^4c|JSf50}Xu(U6C;i2lfb84Llk4hV;)#15k__eK9YEAh4yNBT(=I=Ff0qR!n
zGSe$F8QN1%3Bop9>(SD|THc0q(ze`=4W<2%3LWPQ;(pPKk!Rz4*Zt7LHS+MNqMmPA
zx1L@ZfyO}so`+Tf$*u+_yZZB!iLPezt}=Jgw;gaeA#9p25RA>W<sSgabRC@q_7rK^
zwD_O24-1iZaLcjQl)O|}ZM?7c0F=_+NoFQr0_h@vyTrg3ak&DL_kKlCr%n8vtctTB
z+Cz5y4z0(`-z@5@ttd>2l|lt(;|Ub3%jC$KX%aiEP2|Ujf=S2f-_-GAsP27A-mWL=
z@pxO-r1iv;Nb_peR^sB#g69&i9>GIA@cQTYrHZhZw$z95<EoMQkY6AFJ~ai^8=u1;
z`b>F?_V#nQgmF?-m*Y}GUX7^Yc@(oEOES#HQC_<=UuTPAS+<V*gX)aWD)yz%Z+I2y
z9X!<7TQS*+cRqcG&;7oi)%qf-R{WxCoWAT3P^d{wq7#0D17Yqdv=H<W_%hlGVBy%I
zD|GhFmTKB}oMsXP<I7g}dyK=s)LlA}7QZ~(pgaFjV-jTicoAULxfroJ23ON+Vok(0
zZ9!L;Ph-h=z+o+FVcq#^4f1)g(HptXYVsBDrbB(T?=Tvw2a&g(iq~OrJZUr2I9zWn
zRm5*w1w7gom{IYkv7g)M{x<xUI9x#05;;6s!fQpZlHDyz#3~t`cWrGH2jj1R9Z&W_
zZTiL}YCMD*-1DpzKrb=0Avpu$`M+5pcqn+8oQK;`nQNo}Ll8Fe&${1BXD{+>JU6#f
zVO}%WKo5SAeH;pu9~A#DYi|M{Rdqd%&q@YJc!OpnKq65FL?Rd)(S!lXgBf`PGZ+Ol
zDhe7!6s!?uSZaX8muM!}r_^d|TUxE!ezn@xY6}4rvp^O=6XM3=0=T_lK%z(%mc0Kt
z=f0UtLg4Gy|Bugy%zO9Ud(U0ZJ?GrBJX;05bEpk-y9x9Ec(8P29IvbH*8oDl9qS_M
zB#@x~9<s9(o}mnYMVk7cRdgSs=t?_w6qg)%f!`J_elg~3jN2!llnTCwyuKa{8=~GY
zm)bODZgdNUFU8PXEpBCMCT@tgj%b?P)de|6@tj?AFemEXh0nkwm$eDypTn<f!o6V=
zu+%LkJe0rjvG2UqXRJPD^4(#)Gx=(SfALrOGnwVsRAH+x-6m2z7IEbJLg@>5hw;9;
z@Ha3HGg;!X>q`Td*n)QT*=|4&nb6N};w73RwZ>==Hn$3*s}6VsE`JZCqwm>^VBmIY
zj+xbjUY~=);cOF^&nIj`Ax6Zy3Az4-p&R;zt_`iQ!-`sATNs*e3C$lMh33N&nx78;
zQ^*HxXnu0|G&B(#gyTGhyAj7#79~D;gJgi$zGryjNR07$Lvk4Ky#XqOYN>uIkJ0pa
zZhy6rxEmDQ2^**PbC(933jzsAPE<ItCT&3d)zI{QIrE%>U?Tio8SID6iY%fy3P_U%
zG>1X2<W^GbnZqMu<Z6xsRXklZ(p{Kyo?F;B$9Wt4^*Qt1m~t~F+=TCWGLpBb8|+3K
zboK3LN#imdU@jrZg#ejNPmkOagS<p@Jf=hV!q5})LOs3S@PCIql|xPenD-FmMLOhp
zG0695jzoeGxIZ3h>${XT_x}p9`5A&ZhLt*zSr1K5#;FiCs{)RFXhdkfJz@sL2#a#n
zuW73eQ^(vyi{E%tbWN(?iLkivQiNNO<HblqD@L1K?|gx7fo=z~W?4L06Z0{iXO}{f
z%@@i|@r9&xvUKErRy(uWNFji4&J^|O8)FCCS`IG!1wT0T4IKE?r`+RQpenG^*R@Sg
z$rZ{Tqut)5e*8CF{90hF+|d;nB^5Md)E3}UeTuc|Ym-4fGy<5=e6Kkf&cUR#H-PdP
z3=hd~pbxm+km>J`scmZZ+TbXxWf<1tz{ujr<aVI;ytKFxiYHdJiP^=03)OG(Vr~5(
z7eK$S0a-d0TN3y^#u<&qZkvOFo}1JU{s@V{*?$74^|C(@3T#sEG*LhwdD|GWgoN?(
zp*sa~;Y!{xVB#fp(f@SI%li7hB<{OD-qEq<nWPkGiRy`F>#GjmPpe~8G@GPY`~S&g
z(9aKM-PL9?Y(cFnl(|---A}{{<bbahhLE_Iko8r_qE5mH8zF1iH#|CvQ^`a3*P*m0
z@Z$*nagqKpp%N`lBFnQHCB^Z_^=K@d4D3ZsCW`og{txsAyzhd{k#BIM6~}MZV>~{|
z>;>voaqRsP1IktDv*%@&8k|8Nx)YI4YJq|9w<}=v1L@SS-h|2wX!i!Sqk#V%iW-Aa
z1)%e4!K)YprWnuofK~nX--t@A`PxK75&r(i9=cabQtKb#i)-)F>(GAxd3@fp;ulEq
zR7OP{QpRqn{jgMh(oBmqJVo&hwyUoLnImjooFeF;m6@4Q5;yH_qag-kpWK<e`a-$d
zuKucbi5amHku64&6DW?ms5v{9PJrfo{bzh`SaG?q`C%xPu_LZc?crVx3ZKG&PjYo8
zRQEDehhUS0%}Y}RfF1rEKO#VF)Z-l1dGDv>Be0;7o#?OD=Fi&iQ_$l?Mzdd-`oX<E
zmO2XAO26xaWfv_JOAZ-Y{mLIvdM?(PF*Vo<)8_P%&l!1}y~jR5%W2(W8tA+Rn3{cA
zEneJeop7eZIp%LxKgXcnust1Wra!i8(<T#!0lZtbKVU`q;K54d7=1uXL8%6qHi>;)
z?>R6PvHbKs6iH+Cp3oi5@YA50p2J;^#fEGC=9RD(Jq>lRTn9?Z+y>yOCjO63Gv$+w
zGwofz<+6F`HxhQfK^jV&r#j#Gad~*asY{^8RG4%D@t55KVv<6C6G=o-Z7=!r@}Dbz
zE<H>B96Ve8e0NXz^X#+b&$&J2&sUu#e;(0O{%ntzKU06qJ0zG^xx>EHgZBXxY-=!N
ziF^<JOJ6!P{sJ;|V+vlY;fH14LYLA>1m9o3)5H>0Hb^oqO`u!N$Z;;*_Aw-MD)1Vw
zv?|yxX^mc2W5AL<IQV1sV4vWw25i!YuL2%{T3*n{(vzCKEUYd#6kUC9&WNqP=O^;j
zx2Svd`Ow`ve4|xJ&VpbGl5^INVfg_Yo;{VMWJS(QqR=QN+#}CMm*K^l&pBr002HvN
z<3da%M@6s_-*}l;B9Z=ViE|NB)rESDYFv&{kKyV_ty^|*?mnKxKEl=rclYsowdf#;
z*rAW_dt-gvlEeEr4g2^F9(Z4`#ih)<xrw^zs-}p19|+T{qhYPGz0qz`7m<+BKI*&q
zFlV27L$mGv(6khPXeRA0l=v#sQb;zJwC-=@|KA&$rmADQyNBiC%lJv&0magJv0I@S
zmHDx&N15}&+Ox46*4te_8P;Dm>afb=$`Fr5?3LW4>DL{}>QX5-w)3+MhJp2w>)XT>
zIWSRec!8L0)}davgR|Xre?ldHb#Wt}$*V}Db)m#&lG^0}b<%eTt6+>lE=VyTKaSj1
zoJ_)wra-o+ECmY3(P_!*;pwq<d~&S;9gU2JcH{>7$#<m~BCYCo9h$Zd8!-WG>pSpi
z8JhEc%oRd=`c+wS7E=d6VNLll`1c`lfYA~dhDrzyKa9ea>XL5gw_Y0+tK{L@Sb%F|
z5$Q0-Xi)6$E)*6uXx{c8eXgn%TfOYGSNNAEwu>o=be(c6(9gr}cSib&?~u<m@<)M!
zfa_C6VI<waBsR+i^-K`TAE(Q%uow2^)c0VF^4VTDO27uGtG<A~Vz^Y}#g|`ilQJSc
z^YQT0$n~+$Bv)kVJ!N>C2t;=3^F$`OQ3^Z=neFXT`*HSJ#=eLNazciI>ruU}7GS^x
z_G{*J?O@gb1l9DNQbyQk{@zu)Y@$3XC0)umgEiDc*TN@IlS<e+2=Ima_6xk!7hbJS
z(%zHTICY(Nw{XKRol@D+@G!!x_2GBBP^Y4oA2p==xc?O|+v~*{{DIVNTLONZ2nkoi
ze!dzmP(Ss==A&@}AfB-}-;p+00zMLfpprJ*r6!Ti8ZCx}?f6(9G4&9Mu_CS~jKOWm
zAv57?JBs3YTn0#kl(4-d=d;fYhM-kl9-$o*?+31z|Cl`5W73|iqw2%oqUZ^rWodr)
zQa-s^^PSFCZ+rxw5>KP_%ku~xclDBayQC~hF-fwPuoOr-zfm=>!i9oCx&pt1oLOyk
z=81l0`4JuEu_h^FCt%yljo7)#EBUn?)8${^{|bK1fH3Q+i2KKEER#`-w|vT+F-A3!
z3Irw0ybUwMltF&ED`Dv$fc_~ia!Tmm1lCrrE>7-^3~`K$oGTG2QXGt4cSet7^>veU
zFQ#TShO2P_{Dkql>KlFh+{(Q`W4uLiMU^!G{e}f<;;;A>D2$;IALV)*45Ak*#&Hbp
zr!jnYMQjX%cu!SQ2W#5^qvB)Nzi{?I{<xeEq@oYxE34uL(#QJs8piFEJ?O)%t5>00
z!)=mW2hG^yl}{%weeVCsP-gz3ub~`<sW_JZ?PDzC@fr@NCrg77trXcYqDyH=_2Cp5
zYztpkw))OW>~dlkj_1Eu_OT{?%EChNokU{1Pty!ktc=aT+LduL@LqHV*pt}+LcN>v
zJ;^;J?ons+SBlt*5ay+r)G0hoq6wa7Hmc)!LS8H(4HJ~t&DA8Meoy^6NVSoO_jPnp
zQ&~k{KBn+Q^rGIn4?BC@m(k`^ewjTpR5ZT94>U6}Mj4;r)SEw2jlsApcAyvjODpCB
zNhwM&&J0Z&@5it?klXwb@zepHdyt;{@fA4h^<H_qjY@cNsD6t<%Gfa|eKqV?XAf{s
z>lIWQc?H+^09Q160c=-T9Wj7+>-m@I`7a`X+r9F(D8Ms`OP`fOTQW(%_c1=maR4vT
zD@fBT_-J`IKq!{Yi{Vc<(4)(EnGbbA$6tDu$>n8odz9I<EFK3nsSoxnBk?j)k1}Oc
zCU`wQPmbpIVSbj!^V9qT>k1vo=Kv_WNcTqY5lNbOCG3XYiS6V2NIcY6;(L81R`!+n
zQeTNrj_+%SuIw}QZcKG&16JsX6!5YRwrXv6pe4Msix+IcUU!^^y&f6juITS6ZA2G1
zCSV-g<u*w(c{SiKvJJs6k8}EOfRDAf+1^N2<$VhmE)?4%-FgERHB|>pN}}D==pMUQ
zEUwM;ln=$?P!-!(*0QD_RAgLH?`0SGL#TFEVG=9)i{-}h0mLE!i?VxZ<aqS1y2xqw
z;N`|%cg`H=n82NWX8(ymhk3RBUackuG>3o!v1|8gI}qpqvD~Bs-Qjh$ER95Nl?rOS
zF5|LU+77AUq;i`PsQha=Fs-IfcwT0RfpnLrCP-S7wi7@G8@*7K(crz@7%}@V8LWGk
zh2H#cv8^)Q!d75MI*IEF>r!_4<gLO}m2?MmFGUbI=@p)uj#6T>bQI;o7NPW2D?RlW
zN}uOXDMIN7RQhl#hM3zCxI|QDS>3MPLfO-pgGoRF3gye;S?(MXe&2*a5XGE)yYR%_
zRKrU9K;fxd;L%$kp{gL9BJ7!lX4-;2XJKTxyTar_8}ad&hZ()*kKN@4zY)0M{!-4+
z;DNBis@It!JTVBKm6`p-94SS3%E)U?(QBR4klm}w{g!i7S;Ifn8Af5*Li%J2zi#R7
z&|fXoA>pa@@POi=vF+jAC-s?AEH_mjFl97U;!z{G8^WLQ0rU%{ZOFr+>i~DgZlQD+
zA39TD9{s&mL`VF$QJf}hGI`}-dVJex2+a4?J1iVerNVFc3TjktO4fczDjx)Iu4>__
zev~CJLXot{u;}1?kCrktGT)l1Cn=DgDm<|l-%3`dqPmUn)J~o&wJ5{Ga#KlZM{HL`
zb90>9P7T|)xGKS&QRQ}37nw!#=WJ)V)4-<z0Fg<Kob3{xS_4mP5XA`vcorPF#bz8i
zR~Vi10o3GYfb9{Un9JLc{L%<)!*sF-%1y2uQ5LxopjV%?#y~4GF5HZEf{Ux}fd(vY
zyhk)QgiH7eeo%PoO<J+*_<-Lgls59G1fg^+KEVK&;^M-kx&;4R$!7x>>_PH4E~R2C
zx5INuCm#`X=Acz?Mz3;_;gBIwbiFS;@e<w}b&e38`hy9>k|X28-MOOE(n6r%NmTXw
zQ<@fDM%P_SoGX3o9%muCKU?A)<HJaTIqRuNU=rRzV>A!CvcbUgIjK&e^aeVk6L@~s
zRA)%{8L#zfiB2l)V|C$g&uCg!OQbfssgg{wO@)R>ahqyRLt(Gw|EcjD%O5R;qKgL2
zePTNb!^X$$sN{xTHCCLf#)pYclk7OO58w@RCp7RbG#RfBS0M)`A1)M1_Zgyd^}0Sq
ztMHGXwgqb2sI!3lR&UJ4=H=X(&UOmVR`<~iGZ6|2ycsZJv1T0R>o6Icfigf^U+v-U
z<+Zrwo{Syg@x*jN7qj;F+Jn*gvcY_Xmz~D34==$VeYBo5#Elj%N;WG6vmQL7=dl(2
z5~jyCWbJ+UGmxOHy+UH^IMsvQ7{a>|$n9e!zlx0Yv0@^1*c{C3sj%TenhZp&-(2S|
z?@!$wAmt1TekGDV#j<N-jOK#NdNBU^Jg_aC7>o4VZ43xVI;1_^Vi%%~VXhRq1E$=X
zU5Z!6+og~PR^&gPqxJsRbwtqY_p?r4$O8%A`$7-G(c`zfS&N@F00%q-C$hhn{Rzn-
zG<f?H(ovBFcp#-L(o*o`JUfrJ@7<)QwJ|r@$0j`Vve6))H2DP2gn96|X-NVaqtA1W
zS%@Cfm3*SIb}kHgQm}+Qt7V~Ru36{~-7%WNyupWI_#qJ<F2)DjLUe2n4;Fa16do`x
zc?vu`MeSqmtpzvXdw6YJWuJ`R8P`sho&)Yfz)OGu(xz68{t1$N5YEqPb*>7_CF7t6
zgOMXQj)D+e2=xiO0KrFwN(Bw-uRkRRe>x*Kpx%JYIItUb6hhu3F^>&f0UcrG`J@Eq
zCf72oouqltGPjbiMTUFjNlwG^p%wkSz_e&}Xq}vZ_(0Tlddl{%Js>GJw8j{HWq&k+
zA(}(_m>>QJ?LPLPjSqK)88-`Bsrd=p%%D@@l*o3HX7O>a`3KRw!)tEI?iV-?Btw-b
zciba9l}}SNn(%7JWkTt1Y1ej$fs5d;6qpNt{ehA2_b#FAm)!XAl${5r{hw;0mc5d)
z%o@2(YYbcr|I^^VfUYCZ{r8(w!-xjoygR&xu<^+^9}EY`#YCV#{I{X~BqdAXJ9yoH
z)a#zf>$X7M<~Iw&Q!roYj=*xsypMNuu_@ZoS@1OJvcPO%Yl9c??xwA@dOgZ)BS!A_
z=OjCq4|iwZZWKzhAZNuuF9V{hw%q174#B~OS4cDOizQ(F!I#7Bi2cm_!pBG}xf*!r
zFn8nHvzr(X_)Rg4MmKNX75<%(_vfClLQl=|;YC2~AdWn26-txop`TEC5=I%e&?rnT
z8$u}56tK&k7NM*^)^uwa6%*fF6~3M(W|eK=THJ1#(Orf(Lv#Ti#Srl{43Dix>SXIc
z9D)t+oO1}Uh6*#Sx7u=JYzTx3bL=gAGFE3R)k(v8_qpr*)X+zrlz_u(j33rl!swq@
z!$@O|0c#{3$7*)uo>+o_3I51zc$oqJy#QHc4eHl##{*9z;6o62px~_eq=fkSjAUY^
zUJN&j0(C!Y^0N244{h}Y?hO@Pqj^Zv>vL>sAPKiv_zgS`sv>5LFTwm{q^$SU<Yqii
z0DUU=X2u&qavD8GpW}UhFMZiU(i57S?_*&}@$d1V6z+R3+at1e_2UmvG;l|zADzV#
zCyR&&y_<Q;T_|ZR%^=)(Z=S#GE7YCx?D4S|NC3s^AupitX6`(pVqj^bQ2Gfx6?cvb
z+=@Otg{_V1U$zhtIZl#SHyf9ac4t2VQ}=7iFJWW@975<~CK`fh{${}UnGp*nRG4XA
zAyc!YVa5i86`AQr2Mxca%duLK)fc-)25sE1MXRm2kVk0rH2V~&5<2T=b-tL^mU_i;
z(s~kjrL}-X!&6W#E@yS~aw~B#p7Pa(vbJCjCauU)AIXfWr$(+poO=LU<}S~XGFCUc
zvsbs09;&MAp`u)Mq$*{`j>-w?aLVYpJZcYx)Iwlzv-?y?<sl^WYsxX4aaM<P_0TPq
z;&kKGQ$6)gd0C!;I=w&cd(8I*KN04T)*~)Q4?Cc1lf~aQ6kQqX%l#wByF^i(M#zDz
zs%#T~{74;D#pO^K8y!^$lGG_w7+ncP>WZ&31TR&`#i|38o2YYV>FRBObl0te%18KE
zg9nx~S5X`O4wqR1T7KgINv)CS=ox@GlhYa^S#pKbw3t4CmsR=L4*mMZp}sY~TPg1O
zi%HYQDU#b43EiJHs)ulZLuPWY3N1-cOP~ZZ#Qd?$aoXcAzV)4zC&~9V^NTrBK=ALu
zn>jVSJ$fbQ8Xp_LF&FbIxtVk&r?1auQdIe*-m?d<<mx>tzh}JT0Q_%Ohpqyo<=?~h
z;2pOgm2K&cyE&^-y}4Y!<IY2qn?7|{s^WB4dftQK4OsNL%v-BpgZFhuens?Sw2oN$
zF;KYGD;KB+^{MVmkVpTXN#7aeQ`IHC>*>2K2e+N4E!(zr*FiW>vh~)Lk(4~Ux-G8X
z=gYUNcfQ_wH@PY5x}T6|eOw+|d%04<ULJsE)%%<UNROHoT!b;4(>Eh)f%k+{@Um$S
zUJjB`fm_&^nlF}oiJyy$oY}g(JX1#;A~&(Ry*3I*o_y?2NWK*84PF35bym5_AZ)bc
zkHYeiTd4_IE$WZ&owpI<&uv7nzAEPcgGd#??7NPGvG)KYe`)*)FyzSz(SAa4>&(Y_
zKjj0)$j#AS4mv;h+kY1L-hF&^SKmNP97x;bzJZv2QXfaIkL%-ok45`v>h9y%^MhaU
zv%vT6<My3>1F>)*7n}o#<tO!V${*wUD3?Y1XzA|b_2&own@K+heD6LU+|f6XVh&{d
zq`tQVe*S6u@|Hiu?aM!w>iZJQckj!2=WoQTuAkY6UVS~$*f$s(2Q$^x_kh}d(tJMj
z`?&dhOODQ`t$RL~pC5ef#GeDccOM6z1Bjgix$_)A>_4fG?76r;Ha!~cqrJP2PdA(w
z{1-Vt2Yl~7jyngC1dhgkmDBffN%+ZU$xFYBTeIJm=xa8i$64}9{dpU4BKv1HqE}zV
zbAY*pgL(2CU~c)zeckxmxW1k$*86%(kG{TLciu+y|GACm)z`V_05gPxdGXr5!3_Dy
zeckzNTwjG~Ux)PQYxDN=HX`lkHlkNw?>`5abiPqGT+{bPN&iXv^vh@B_UUD7qx&?y
zd!HWOc3$w;{w(mjvZ_it5h)8#SJlb~*P_Zy?|wgg4!}AIwspd}NwBQOY@ai`CvZ>M
z{-9MpH9u%2-?ywf+7zAlVSCm6Cs3-4ajPgFCxd#~26wk_ckOw%^~L9CU&7CBpRl1(
zEj<-&VsT$hjQ#VmM#;4!kdE3<!+jY$Fa)fyAyM~}AeOraYvnVvpn6;{CAoGBkG)B3
z)Dm5_jdbZk=xq;JU&#l5@@6Sl8~rToWh!uQK6bEM-`C4N^|Cgft9?Z(_m@K^ccS(Y
z8gt+9xnw>ex!RY9Njvlt$@N75b-Ds0q>L}Pe;sHaH19&*ymI%2)Vjk`#u=~c^okdK
zoO=x*AFI3n%a3{fJI>|iWRX~M#11r%pB;oI@=#R4OHd0eRsD8EjEgSWhMCZ!?2d4Q
z0NG)TzQ7=2`y~0KNqCgVkkEQ(Cf){y{^QK1-?yE)7WCo$y3>Zg$^)7Y(ojb26aID7
zXAXO^wC?dhozv}J_P#fxPHI0PDTPK!?y{{I?PoAG+c7#%MHg!DHwoot05^*TPN&ae
zub;M~B|?p~`#VYa*9ot=O=2I5+J|LTYp&snwLVu{Aj8kz_hr1_!ynAK<`*Lb;XGg!
zSsb$8bs{hZs_<vL_mj#?y~=yByzeKM|J=*YEL<nKjxQfBnLm?UpD(+R^lWzWN+a-P
z#YOAjlh^gxilm;OGR{b@uH`(ji4xaB{x4R*V7v$GC~%37)%r5(g%MHsTo1bu;M+NT
zSam^JV#mXepfar)>T6#%68-D&s#D^s4nWoIONZ<Jo^a*$1RaQY5o*1Zu@h(2Rb4b1
z*84L1tPsrIjL_ROcc{+e`b>~7?IPu&@Q-I;mR+AMv(E_4gBcFJ9?b<s1^M(D%ylgM
zKK=5QB{9CzB&M%C##f?`@zoZ;d~46PZQRFQvjzu9Y=`<=Kv9tg(AqXZJz1ln?SOpp
zz=~-SOFI6f$-o7GW&0(;y@yOceQc-ZD5;3q5I>Y-HN!y|$RoSh>q#b=A<3r>EPn@6
zu7wo!$<%Y(3#$*O8n{*UoDiA)4NXO>>c~fGe)NNje=yJoG(+A3`0nN?0?PEVJ*dhV
zMr}^svEg{7rKY6DL`2p^@{oH8;?=6M7UB0OTKl|P<1^3#B|gW2T8WJ|0nfXxZ5o<l
zRT#LU-pq&3X^Q23qqiy6)4$S9u|mI!nqrN3<NsocHAKCAc&zc#Kxnl_az(t3nxnlB
zbGaxmU7KlJIY?q=VS`Q5sw5b*0oc-UZ=sTO$md)jcWS{we%IH*RK0j4MJ!KrdbMpN
zxh?J-5HLmlE!J6_)Ww%~aakY=|L%wyZ=J_}!%sSo8M}RKhj&ZV<N^&DMpPK5RwVbA
zwCag9!CTM-VpeHYV20FQjmEISgEN%m{tHVEX~M#ilO4i}-4Zj-V36Nc6WmU&j%3y5
z-5j;;SUy;iTQ#4##hX>*EvpSwM-rv1s#<Ayl_c-cYD-%JmzA~!MoQI(?eIJto`>kq
z0z41okCxK5V7bIy&PC5kn4h6-MuX<q^0<+#%ZEyeqx}S~>;}#8+sAmK1=f3WFB^ZL
z12$5GHO=w(<76ka3E8gj*14!N$)BH|gU>v_y83clMGdH&xM3<^P{R`V@7-_n-=&-Q
z@2t1zm;0N#<SkV3a_v|)u3LFt_Z7rQq-w&p)!G2zEj=2q@ZOyrcN`UfT86-W1<X1S
zwT?~N?r6zy4^{c7UUG1V`f*BZ5!PbV9DIUx&8%W6nX?5xlR`!^D;rZAU+&M{<yen$
z#y-n+cTJu={$FT6xEDRPCXIw=_2aFCQfxTb7hr;QD@nOWm&5!hN$oKH>L3koAA+}`
z|A%(~=xr$AR?>FjyegA-!B1pRUjrsjuKy<*z>R={Tj5E)p92$6nXXY(@=TiJZ;&3v
zT5)4b_|%Qk;rP4wTn{_I_PJRntpW9)vkiuI^5jc_e>V!H7^0}&ItX9jqq}^R86E)v
z=~|FUPJpC2t}ElHmF`>cs{1AWwd)`FHJ!e8kl@zG+OQRH&Z_th`1F{KV+mK+(n7WW
zWd?Nr6f{M?!G!q|6Ew$xQX{Fz9vO}FN`eLySkmiMa~=Nz@6XWt26*?mjt8wiml}8*
z&M9s}&3`jvF!i=X{s2?5<uRI)XW)nX>`)w)Jn2<Tx*C$!p+{bUk51u@=%D4R;OKhm
znG!xI{KG9b;#F&tIve9~u_n!NDYRu5PC+3a9H_{g8>hEOnvdayr1KBYFm1+geizcf
z7%pk5F+7t#?IJ=Ll2{EnXzkw1UtCAj_{vEX4U7C{U?u8>U3wgHvRRqD3>(Sz2CeEp
zd4cJZbT1oxvmOAtyV8Ff#uBpH(={!C!OW$Cq}+50*l=b9QWp&+Ie8vHf?rh*G+|CL
zCAc@MO5TJhb<pzuIVBi!6wajRol?vl0QY5-DH{#q76bwd`?V1in<woh_zhUH_D8Og
z3al*zroJSbz#QJh!AL2=FS7Q_l~FLyaWJ(&!lLh2wRs|4&tED}R)CQ;LLYZbhR$K!
z0Cj;Y>P)2Tq^7A~Ulz@Jx1RNR%xb55xmeZ@FzaE!Y3tP1TghB!z$OgSpgx9Xsg4DY
zqJI<X(AQwoIcsn&TCGr<nqIBvrG;=Cdcm-6)sx6%dpaZ$^P_G*#T_jGWq`qs)VpBQ
zx4E55jA%4oC~l&tae`-uk5%C%Qj_WoqTvp%R$!yBSS>6_^<(%G8OA!~w`n8{K^uJe
zFC^|#cjAJ@%dz#&O3oDEOvG@cI=_ZrP=s&d_9kdv1k*(!yOgA(YjGQGEWvGM-BWC$
zOT&D!>YamnJw9i?T6>R?lJg|F$eCvdh!Wb08K?r?S3xE|<?<^_#roV@UQarzvE}*5
zHitoVS&iz$P*>zqiIqBe%rNUc2vgBt<to4p*6*=6v(BK_ShuXfw^EAr4zF*+$9n72
zkl5d1sH(R<v4(F-C~$B-2fGNKKhvKJHAhXc(GaqphJ1WVOf|T{G{=j@wCg|k4o_DF
zUgXIiqrymV0MEA%W?6ImxY}sgjG*Yey%WonY{SOV+D}>y*s-?zUUe>7*ekC)?J(^y
z$gLC-*DB4|U1XFUAWh2-_5<4sbWgyl2CK<cN+E5XKvs)biM}S(zrBhE{)kExk6lvy
zcGN?nk<QZ`{h<*)_E)rzBF_b0GI&>03pg!sE#CfSlX4e1HCTXCo{3NSJ~-Xs1#NTf
ze!S44Xl?4Jcf+KlO03LAuI$Tr&C$14Xmsffx>yLfJ0OKT#hT;(wMH07)}c9WS!<*Y
zNSedFmfWn^%d}vkd`g3rzmwmiF$Fu1-V40{;@%5dNgMa}F<!PT6Ys$1&A%g_CO|To
zC8a)!3-tXpxImkT(7s?T?Mm~bxGrjYoOvwiD4;({8};WkNbunL=YESe4}lpJ70IaP
z(ayfO$jH->-hs^lMdUTyZae<|JzYgx$-d%o0CNBYNzxhsCq@mmG{<FYjNDmGJ+WKw
z;dB6N2TR|F4k0k+L2sx3ne%Joz|9~(yU{!T9xC%I;%r3w`hE+2@0ED6D^_1Oka5wP
zVrNT+fLG&Z(~rZ?kv_0zy2Q54?xF3M=gDgGu<u0H<_;Cw;6D%{g(;#kD?!bjiP*Mf
z5b}WsB;^U(*J@1?S4SR*OGM$0E>ypsc*x8X5wEF>^u$e&sJa@_2G<>$@MbakereL&
zp~<zd+2H4O+;d8jH3DaP$Ge6oJ(5e*3M!zuvonR#a!$6aC?@Yz7^#JPc@24&YteG^
zl%#z9vsGR5ALKro)ZZd!jmzoYsf3lTp&U@+F(bG82)eO@^%9)WP{9y9HU2E}N*AF<
z4ccAC&C^X3+cfFeS`H{tbpRk=Xu{`fsTY?KmcASoE%0SR=_N#?ZC=YUA4{^q)8fWS
zAwHPQ;e&TLawlA~7TspCn#EO<%nkZiOkaxWBbjw2{31tD&t3OmWf_<`R7ztVfm!<x
zeZgOTzLz=)9UxVOeaPH8#Xx8QHnGLYz1FZM;3QYALAPW`4ljO$oTntabN)kRg99+D
zyS#7;>XhMA=GoQq8r`_-VpaYsR%HsW(h;k2AmybxZTe#->GQy$NbBpiL|m}&Mhk0F
z^o>^3HT1?^AU!&r91WE{hjLD3jTs@cdgW%ov58LA`L^yB)QC!YTJe-|!34MROCuVI
z!JPkl3kga+_wTo3Oym%U{Z?k~hMbCoNf|<^3a=Aui~8qItP`HZ(A>pSCI;`(OE24k
z`skC~dXwGeJ<%E>HYl(bIMw=!go(AQf51}X^-_}w=Kop*EnWwT?fawOg{=v?za|*R
z@qX&%=3Ekg0(qdixYkx$?`8TL&`Z0b0~{UOt^x65dw|{|w*lyIKUA*jRmGx{Xr9_D
zarVixGfwFvEdvXag<y9S`)<9aCC(Jx!%ZgO!8}0Bd)(}xe9|bCEynp|`%$Wn-XI5v
z+D=j4d0NzJx1#zsimV;14lQTmw76aSf?1+cj>^LDEnIyvLH~At3)%*_b_9}vzTKIu
zY2Jdcn?3Wdz+AH-asi}m>`t2mX`<GkFjS;tb)r_&4(U6@>dpj_RYP@da|0yD+b4*u
z%AK*}_wpgLxu&|qBw}dITG8Bif7OGuEK6;p)&r5~a~Ae(Sy8>XC4o#UYT)EqDP~le
zYuv7ywFw?&j;S+ZcAJ~!PODJXVnEI&uH!qZ!z0YQdB-nP*m?ThpOyY=C3ZFe9&+Kq
z?P?aDEHQA^`^Y1#b`*_sg4Q56oYtynkkLKa&m80XF2wPyhVgtnHpa8N({PO4=4YIE
z52I6-i1Iki$e(a+-e09ctIQm^iSlV<aI`z*c>GoqkcVaXH*LkONK?#1a&?LBt__x0
zEs{2QkaY=VeE|Nd2m29!F^whN4zI8y^7PspDh=~STSG~1NM1F-+@K|015c4eS3}XD
zj0Vj*2J1)~P2f`S%Ts3^+Mf0vwyY^qs4#Ysu>h$eMY3n@59Qm{jD8#;6#&_qvz|I0
zOywGe3xKjp_p-(4_A(o(D`G?0iezq$?wY{zO@?5iYr=2fS1A1pB<7?oUWLyW7?BxE
zn}IJIlWw3ND}QrLfgd-dUb%`KW#)y@8yOIqB6}~G2_GD(JVTnD|5^v&FXYHZhMaWn
zQ67;lvX3H8{C;F5<#QZbX(EkvH~}y_Iod$odpE07Q%|E4v!sW4R_nbhsYUnDkK<OJ
zn&e&Cvqf2r(1%~(N2-%ePY#YmKqGnnv^305ZJL8^0#M;+h#A}HFzIcHYer-ZMX>X*
zua!mp-Ei{DU5i%>@L&?zc2T*%pGUdN2pRK`PRF|@WRWO@J%WG@&OU-6qS}bgJ4GSC
zlFwCwkJCNa{r9pOp|F|)C75I1&Cqye{StoI4XL85dG)2P9l{g+(fq~LAUtJ&2iJ}@
zBjIKB#TcLN`!n>V=(uag+GFsr#)kR#qKm=lHryK1E%-NquU^-VpxtF%1+@!*s%5p|
zw;-SXzP>i{UH2Nr2B)j*f8<dikj|DT=d3{H>2k7#Qg0PWs5FYw2CDl`z)@gLfj9nn
zSZ-8n4$Jbi(O5)%19Gn82`8rLnYw)?Qu>)(^5V5q-E*2eB~oI;lx|OneZ|kq(#WMJ
zDTJn|FIu`4LZ5_QKwlq46RbGj<s;XV^O(Q~J~qf((wPhN_3A<D{oMs4JEY2{(IPox
z_PH9?ekpE2G$&PVD!gFl3F)fxsDth5j+AaM*dm~Ex$1hrd3Ydrlsv6H44Ahwfer%<
zCs`}DAC5l^YU0iU#b-PwD!H36(oI2wbbsS-P;oV52Rs$u2wb7Lan&Ly;Vr2y&N^uH
zuo}X{+*D(Ae=(y5(6MN)L7#5~NbQ05;gkrb>)04R_l1swcVXQCDl!)Xf>OmC*PpBQ
z7^?p%wVOqDQhMi>%Wr)Dg$7TGJ2WjB2)|eFMy$NuOMbGF;@prI)P!cIimXMtzpD73
zT^jmBSFCSA?lR3MxU1N-RIeggA_=0o1*41P7>iDb87)%5j?iqom|-aoO-m#nAminN
zbOXOCNs_p4bqNqT9<ETVr@L{f(pq%<fkU_MmdNWkG%7SR1==;BSG#P`sMhZ_YE;bg
zdtV;fL$fW5TPDGQpH<}!4eT?1V;?@pJ1u?q9Jib~zt8b*+4qaanC$y0Xe5_?4`9|8
zZvvzk6MA>&U)r62etiDr=g2>`JO7pO`LFAl{~W$A(#g#cs-xqT_Y~Bao`O&2I?f*b
z!h=pqM7h%#ybQ=JpE3&D0VMhn^~^PB2~Z`4`k@B)#aUI630S;ZMX_0Q$FFb29UmDk
zxoVduRFZnX;AfU2bl2acp2#v7dd~wZ0+wBOk%DojR^5nlAH^EffA7_QkK(USNjh;O
z3go?Pw_C}zxdsF>v??#qT^-|={-B6Q<&MEC`c)>O6se@zt?2_KcMcMT>#J&|=S1eN
zw(#*(kC9)TD)bv|O_&V?gGG|7?A#Y1es7BV9=S>Xo*aiafnR?uE>C?d4~)dqcGLiH
z{Ocxu<6-eBd3HdoE{ydaGDR8uSUm-LJqcu$92u_O6j$*B(TeS%l&BZKcz@G)m#bcK
zOw*KU7e&(4A#p{oj1^^ukU3U31~($~g1b-XF5GrwcZbdi_to#BaG#GWx+PY$2i#u!
z4fol}*wJwxVBF|Jn*ALe9cXYEe{`tN@~{^lxLJZ&(xD$6<GTw+c2uq!XC6!#a!9vT
za`Lm+6Pa3kU{#0PTvdrU-0i9=8s*NY(rWN&I6PGv$_^R%YXbaaR7JS$N@#X+WrAmX
z4XnNucbE)Ow!j`mT#EHMNCu4QX1Ax_-+~y^4am9;P-O$*aj)XFMEYqph%4PWLyJym
zJE%gAFg+QmA$REZ#P%!ALl9p^YgT+FOCMnwD!0G&;;#usLPG#WQd<T_KO#Z|9C?2W
zqDW{(w6W3SB8n7i5Gk5_qcZ)z@}ne!?)xMtsLenNL~rjS7fbTWm4@Jjn`k<!Gf}iK
zZ6&U7z?0Kfp3|Rlr}`K4*4!Jf9{FGJYDxHII06GB<*+4?B8QU#Nm?V_t5jMn7+UoL
z`fIV_-@z6V9rqdIS5j_Gl~{PIk%UM)-RO2&{o`-A)u*!BE}~hmu(8%1x+BpSy2G;Y
z_-nujot%Z?AkS1-MwFw?l%%~MJT55;dOF#RGMx%b4n@e4v>ouF&-10H4cS~td1WOR
zU>9=%_Ty4ifL%z69N#mqg&gE+j`jB&4O<Wlinb<^>H@M$h2=)T!dkVfcIk-<xAW#Y
zx3e%EO<Qk{TrO<5#po6`Q~^P-;vz8v`P~_c&r*ojU~i_Ye}5Xqll7*Ggwm?u2kiPp
z5@(}IS|Q#vu<I>uwv*p9logtEFX~8=B4PdS%_L9HH9$6Yb0XdTDV{>4sp)nCjbjSk
ztTwBWDA<b1KC+h;8aWl-T|y+!s(PWhl_&|DTj=9CrHqe#%AX3!l5|#@FQZOuGaCc_
zi~HLv+ylOh41!lvz+T)xU9521zAP`n!HOi*mGEl|t=cQsnj~#!upu%fs|Bload;kx
zymYx^=n5NDqfAZ5xDW6)2x?KL9_UpEhB>ZweXz7%VoISdWB>YhT*h9^W$ZwJE`w9`
zkukst-chUH+2I~?3?$KLG@8fffDi*0N^)ftUhklv*XQt3DY9e}9#*P6fhh0idC(9X
zAy?XKc~E339<sw*wShqlWWUH~BHJ$b+z_y;-se%cX?-*guVfyt<d-#pJbpow*{bow
zmP0UJO^Q50n=&(9HGGK$YkT;v<GPDHYzk_1CjU{><WbunKu-#%x`LzXts5TTa#77;
zzt5;EsLrP5=!SX`k9_J(@Pc~l6S171F9eGAL;2L{U@~b!)>{kpZ!Fgm+0us!XjB7~
ze522e>G=SqV7=9&?-sgx{z3I3PT9{o&@2uu+l=TI{v!0}pp09618{OXB^L1l8%beN
zbFy{AX!9<w`3Rg%nR2IfX}CmF&!EjTYK4|)VR~*yN#4dL&2i;I?iTwS1HTeV$NOlL
zp17S7XQkrh5@HtB`;?}I)>uM!Cc1Ng#S|WULchuzojuPvIxsFf-xwGpY{(WiNJdxZ
z3P(m0)Ol?hG^luh#cTf1o3Sf+0;B3?rvyKcl<P0v)P=H4o6T$9M&H;hi;fa6LzFli
zQKA4S(SjbX2`5G_B%~NgB&-;*5?TyhpXd%Pv_;IJJ8hD(#txW{csoFI<c&Hf?Tbag
ziI!miWb{z|QSXiwK8cHd&Vw+pX{mq}z14>-@1W_(4T^iL-OC!p?3BP&*|!=4R|p#h
z3mZhEt9ivx*x<iN6D!Q3`9slW#8kg|m#caC5sXooJv7*aUQ7MvPrVsOf?HtRN{(UI
zu%&?Uk68jk<YP&Jfm%~=*cRTkExc=6c-OXI*L)ct!*D}m?K734u|IC^M>A&;^hk)_
zj1PmYxHOiY<iP@buySA*k9Y<*0QVO``UKI)r$oz3(LG>)jw|qvLDvI0`L;j`tXelr
zW_Dm?kA?5xi9HrFQ9V-aS$+r?q;thJ)+x&GY3h-FxNTe2Dm)n2pK%uhsr<{+D91S9
znr0D7gD?e~($V2q9w`#-0hSxr-`#YY{g&_h2C<T|Y*%0nx-fn>+eNXGxXXmH0hIYk
z%nW4x2sn3Iwf@1udXm?*3-C$jAJABnn$W75+a9AJLqZuEkhfWf`%#Uh48w_UMT`Q2
z`W~GB>)13)$okeo*lp8nA?rWsAu(iq6(2qUCexyIX?RB`pR}%a0L3G0bkDRe6hoy_
z`F&<Uv5`aU6Z~Fs5|`TQ3n5E|duZy^@={~-49aj2wOUFv#2TwFk_wtCN{8YZj5!X>
zgdFt|ARnwnGqFoH>{3!8Jo7G%rY@z;r;lc6bLV(iI~j6^790)ZFl0Jr{4{_ryVaAw
z*EIFuaU^$$t+Hmp;2jfDtClVoNx8TP@7?dwjb!hrjml^qtU!58@+J%<uA<ba-g-*~
zM|3(<)~XpLR$@02ksL4+b}mWp`I^%6H8cKI5%a~$ezY?k&&<S#QF&wlZ6^k<RRgAC
zWe4HXJX?2CRXWD+1ctB#ZOdD=;sx+iOOZ5wN5a}ht<`PpAJ;MqyxZcM7JX0Y^`6<|
zJ-&5qGxXLa>aD{p19<b2qRrFOc>8ul+vhfxFQA25eKv1iGKRkFMWCxBC22A8R<7#6
zgA$XYjlT<hl+BYE<E?KDTqNfhf`io&w2>Lsp#Ez+R>M+y<~Z}a?i{OYC<j(b=Y2GN
zFFniAo=-<$Q{f}Id?^VdvR41g^P>ZmcL}>>G^u~#Y24XdllnA&#cI^Y_%ruBX>IV5
zGxNYOc@qu;w(D}T)}{>Vt(aMPB!4R(Xsd$RbEdZjE<iVl=mi)1VpXrgeDc~3q&V0O
zwzw}t=u3S%(z07o&#))W9d7?^oX!)f3d2l^Sbma^4feTO0#~BTX|_Whe+DCiwFE|Z
z;pHDF3za8gtVG4S4c4a5)eL)YB6;emC7-DAe5cw;6S*f!8`S_Z3Z%qnqe1Fk`u-fg
zMlv>0AfmgkKmti&RfOyu?*bvD-i+7nY!A<HNk$xflfIABH_>}^AY!#qS$A?h1Z~t^
z_{6MFLnXDlP%9MD=3*M|WtQ|SNJk}-B+W4$QspEhyT~hRIBRToth#u0OvO6+GgO`^
z!5a|yF1w*#OvMSTFTpP2Q(Z{_ex}uxpd*rLP+<L3q|-_e*>VbPZ3fYRGDp99;tT-D
zJGg*8hqllNlxe|1fb%L54}@esC^R<ev!_tZOC<)4jfz|3xOg+X-J0X8+l>a6^d!`P
ziYBw}Fh*HEynf%@fM>~FBcps^Ad4jxATZXfImS}no#<f$&d65M`xuRB@=Ti+GDi~B
zH=ZHF3}~gTw;OvYljv*UVLo{X(56Us^}#)HQ%3mdawDgOyW?LmZ#LH!$fM(Xro`W-
z^0$2cb~S%n#NS5qw?h7w!rxZ%w*mCVXqQq#=Wi(C1-`o$?>8DaoqZ6}-%a8N^WMv8
zmKdkhkraobsd$zSK^ahP9D&|XedRZ(Le`{~j)~6VA9(TTECw>jm*q(eH8uG#Z0Eo*
zsHFtCLRUv%kURy$nn8|ZCu6gkJ%(!v`>3(q1bj>`!;&_6`2u$0h7dHz!!*?|A+`YG
zp+$2nqFLFgKEvH#aGo9Z#qGEU;5R=On?cR_01mXu(R6LS0I=>|G~H8fN7b(z5l)8|
zkZ8GZ<*sp;#MaDJEa^(GzGm8PBh7+~c_gN{Ph%xN+!k9iJVbqh`Uv@F>DGTi^iQf^
z<Q8IqRM6>WhcG~)mo=+Pfhu)lbitBSxW!fghv7LOv2wJf0vd1?#V2O%z)>Ek?y}_y
z8{StJFW1>xXv+n;<-zRS^3^$Yo{0vxGQ$`d2yfYSo*aW4U0nDN;O&}E@nkbGnHXfz
zf}lBv>`CW*@$X-w`^w~OPw)76krZL0CtV!5?X19hXt=@E^0qA?<t%yfYIK-{w2O5c
zkR(z;BSt|SSySuDstUJ}t`V~yDnWb&>*F(gK-4@gP8Js!j^<n!iIsSAs9R+q<ww#a
z%k8mAj>-L25MfB+Bt_O-e8|+cZ+IhWm0bhuW{!Ny7)X~-nF0>A<#3c7Qs3Q2+(9Hw
zln<PVSVXzO<PK34kpwyEG@vt05l!85$C8F(SM+lmXv7)xmdoEfh<HlSEG}gA&NS&X
z33@$>-ANGwyni%pn3;5n4aa?U5xpta+b8qGvY#rh!`(sIhmFEvbr}Ma_-+}?iw{)$
zlY?&3eIJ5n;RR5`P*1dmr`lutYmd`ls7z}8$7Xa}LV-7Tst2Cx-Y4PNJY;(}E>$ll
z(!GNgmNG+7FTXfWezq<gCqKJg?d*X4^x-kI=6N2GfG;#$Vw`seV7v*zQF@~rPQ;oe
zLbIYcXfFRg%?8H=ypDDndg0jceBMSp+dO4eYrO85Ba_*{QgaU=MdU({{Q&ep2P)m6
zdu39t^RWbVM-46O!CI|rp0lv%5URBSgLM1Z{-awz(eEnHapwT!#XhkT6$Wtcf3G@=
zHLciX2qwVFhC{<p>>3{weabYWSe$JLTuav7lJX?(UD%YGYI}}aVk>{_DO_reXEop<
zY(UmgAUqozIa&+V7`W2dN#b^#DPqMWXW7x<G)eBr738lVHMdwB&wUyyb8~$dzn~V`
z#6SLy%iD-??+vIQ0+P=dgSot(^}L?>Lg~|-Va`?NPT_T_H&I1_>1S0^{V`Qy3f={k
z*q7a&TdD>6K`mDV^YmWx8fYBUfmBU!1ZEIQcVLL~QcWmD2j^a8p3|sp4_o?DPrRL=
zG-$omBIDfq^rWvdV>d(U4Z{fLd`X#>i&K(@a_<JCL9FCHPSJ@F$9;bxEq@ss9nCnS
z&x|Y2Xj&W-#g#LAxA}iQ>fTpz^4mD=M_5Xoo5&d{33OC(7^>`Rx+z-P*#R{&<ANqA
zi!57i#Ilv>E7V=S+TacWnwh@H$C93_<BHg*WBp9Mk)lDq$D97nX%u~oTMjvV%8Et6
z&^}ae<i@M-sV{SN#xg`>UN*?9+zB;as9yD3oW<iDQdyP6%mkSrcm&V40A)N>;+40j
zP(6O;*L_OeK+w__0W`lWQuJ#ykeCfLe7?L>L)3L4t8EpkjpkY;3vS3)r!ySb=?n0<
zy^!sMZ#MHz`nEvLV5sM{b^-j)lR_)<rO-pL3byl*k{FYL%AT|8BNg;{!P(yysLk~4
z@p8gn90L5$hyPM|c^8V+dwQ_m^#R8bxwGS)SC$;<;3CVCt74H34^YZ~oC|0~_xjNZ
z@?pF`*nR@nS~<#veM%*&dU~?HLINxoQUjFwN;@flQj`^x^PtDI7l>Co3f!&{P1f3k
z(kBt;Oqmv(<<7p(7?fZ_|F#|Gf-s*tdkC%lG?cp9+@X;R-Jt>tDl3@Xq0u;aHyL|+
z2ZuGRe%C^3ML-)4<;u5{W$4ASF|Y%?Q0K^JI8J$)x`qv7pQwZM#HS40xC6H4C$N;&
z4JYwNL?}}!^BiX;isva@UY>fWj+fa*-_SMyYRjbz=rMCMLJb)aWI7)2*;4|8yd|fp
zEml7}9Z8Us$xqHT@<#ZT8;q!%h@aJ$pQdgv@G%@b==)&K%zJrQ_J<$g8XFgq>bqEJ
zp$5FK-U{h{wD_g58*&j<V)e4^k@08k?#A6XNWD|v!c>&YJ;7%_YnNJDj29%^aB0N3
z<Kyz+^!VkYyzWHa11-AA6R&mRmC?%-1FfDtzJf!zbSjSHrt$&eQRbixQ7ejM-D0I|
z;I~2De(dHUp2Enu*dzsO!500nmJ16PA()bF2rb@GpqG707c(%#QuUWs7$cXYj^`tq
z0Q2OMB5zU#b<+@CIa{)-We$8+apOKsnf%HMGiF;vh9Xm$Wh0*hc$+g*ajyk}#WEde
zY5crE{d^TF8d<#1BO~45con||wY1cA$s8xu8u@Zt;8i?yr-W7xE(rrGO1$Q&A<IVf
zS*XavnnYPM31xWI3Jc3k(-WF*k=T>yJ&BcmT!e~n(=CBZB>hDSJ;8sq8d2lg{wJ+P
zA4Nt0y%8AZMGM(z#>e9Gi*kpR+tsq^$sT3FuTVS7$orWIJ%auCXtmLx6WIn48vdw<
z?TTJQjeUX6)rt6RlZN_qm)KBZ?$ve$k4el?HwpE+{eb038uLGOc)0|_@l18-$zl_c
zs(&jc$4=8N&~4r}_82CG9!K*GQhrFlK?qH^%~ULFfv$MCii{+*<Qf2d8E(CNFP#rC
z(c8<AlAh<BgQ|+xhf+7fu!Y~DgE8sowJ4wQIrFj599Mp;HULj4pDSN|5{h}Nzk$t?
zc%52OMn||S&mh)&NLt_vEpg?C-#ydE(O&%iS-2Fo0Qbpmz4nS2Cat3V3x|3qFbe6o
zsRxieIGlwGqo78rJ^*XgZIi;6;^ktSTUlr%$CQI`6pFKbLYYWUD~;2HvTOLGX}(aF
zL9!}Gpo$Y)iFF8BC3>sWNQ>H+5fMsPBA(5538fEVQMyR?DWk@}i?}w?=|`#R2kORJ
zGTj_RTHt?3Mtv_8(2QbTi23AgDWX>Aa~)U%T<Laz`Qy6?^F8&I${2w?pm?>x#l_pA
z++|&RHJQp04Yi#kw|a%P;$65+GDWi1FL$SAs;`}*%^omeaGKA6Bn>1=S}ptvW$5q_
z&x-#TxY5BPVjiBLF-44U;1R-6<f}XmL-n4jk9NUST@rwm41dyb4`#yG7~)ZncJLOw
zjJMDa;S`ON*j#SxryikZ2ZmC!x&BP&NsgYZaP&o>*&2Y{4vd<0%{Cwugt8l<*<m+@
zMGpJ5^TfabD&+?jmOHI0GLVfN&FLwTeJBEGLA`%)Y<S10&7XGjFXvI-@v;v|W~p9*
zg~{V%kPOmaepJ&UoA5K;Pb3ZIoR0o|pj!GU52NKmamXl`WEppj>ttXkx>exZY-<2m
z&&y~k{;Lun=VNv3Beml+AHY;J?`5-XOtPrkz9hrIQOG5#mpw|k)q!FD`#<!%QxzUX
z0Tr4BPqPsjsQ=W4hvK+}YrEoU(E!q-<kvGAqrt}xyV=34HW<R?L)3rlk1@%A*h!;*
zFSh!Eqg=#A2-_HeE->Vl?ZhYY!O{e}C8o`DnK3FP3JxLu?l)XW)Djqh!!fLTI1Vn0
z4aeWUjd6%0E<(R1+hG>*!)L^kk|A+ARlm`I1klmI<uJr^vQVq*J++*nm5a%b)++iM
z5Inm=Nv%GUA<~bh@X8Neq{q1CB3N}_nA_{T3Gmxwx+n_z+@+)1eqk~IpQX25NxBFo
zQ~gI6+1<1O*CJF|7YDpNk=}H1z%?$k_ma6hAO~JJ*qIJ)I^LvuzvTeG#tzFbAw<CU
zb-KUw0BV-^bd`>>T-b=4G5lFpBb+jsi1szddWo#u96qj@b-Ctv6jCPjTLuH1q@nPd
z@WdtyajcqS9(?6)FyidoS<*){(Q-(bjv}m4^)pS~-<fsOOwv`eUcSvpx@yg?0f9jj
ziq$T6T(tCiGz@-DGQ$^ll`SflwfBk}2S`1S(=~UGjv@72Jgqv_7vhv@gwpSzb;>NW
zdUzW;sW9>jLrmDajWA!5Jo&rJ$p(EM-Ep5b;2k%aU@7Aq&{py7Wb{F<SQl+4{0d7*
zy=t3&a+W0%aXs#0%vfo`BR4c*s2?ZqBhB$kH}HE(k@H)CGZt!SgD!Kjx3!k=mm^-c
z8TzJH)sv|U(8UQ{qfqXcw0t@yT#!JGcnzu$(FzR_q33OhReLkGOS?Oyj1Hun8*2Kl
zcM;YL#TRVD6XruaCLCOdA^I@x4W*cQ$}rdiM{1E%wWsqF<q&Xk;lCQ<iF;n{V63)x
z;_14$M4lAJq)k7f?|H}WsYZQQ2j)sBAiaTojH@cQVxU-VTbPp-7>rJ6pzF1!l9NBe
zkS_h2B4X#3|Hp}5&DE!pa45*S6pc;MjHrs>XpU#xSossQ6!0vX6XXghxLCN+XD`zP
zT|bpZRIRI_H4+%YUpMmcNRub;x(uzklQ-ewP?rGV!40|ZJ`;;a7fKp@gKm=>vm>Ql
zqHezc*J;uZvv5lkxw|b&tKgZkz=rz}zN(K5?k%dqxI?e<vnuJ|v&hDDqT6VQ*E0nE
zoNB(*&FygQ5<2BT<8LlDHucso?5X?3tgA%W)de+KNAW(AeNX$QB{0E@;TY?Ykx8eu
ziXP6)3*|{GCkJvC=3KEdkVU>C*oEjG=Od`+DiIaJ_DI#I%<cQTkHH@OkuuT-U^d7T
z`Sh$i%)PLJAUmA&w@Wz#f#8s<Y+AL17l}VAqLJJ;Vnl*(`}1>wOEzqZ)L-?z8xvrr
zNEu(K2K4<KO5kF<L>KB%MkG2F>dw-nBV8KwwcImyL}u1ebkg-b`;2>Y{`$k%Io7fb
zNC^_3u}l0wTToQq&6A0y1RiaBlz9<KcVH&h0im?ofYvRGOcI-5%SCI7GotWJz2ur;
zg16v9*z@mLHHM9~MQ+15cCZ0*1_e)ORk3cj+Rp0s+^yJc4^8}VDdQNb4O(mBx^B4=
z^Qthpp(`oQU1&mR3gY%Qq3o}mD!zXTYOV*oSchAkvW?c`dPr1H(fVJtN*&9wN}+o2
zgXkEL_$;O((!)2rr%WSv)?Xm#k&0wfHzZJ{+I9R?fn5TJ!~_FxNHCo{H25{obXd>y
zXP#+4zOwI-!G1zw{rnk6B-Z8KeFlBn7y1j1Vph*q9C{*}n{fgjs^Zu=2t-oC!l^k|
zFA_@sg5y6mCvlPP#+^nd0b2-bd_oYRWOPZ@nz{W8Pu9^gO(;hxr!VJ0I1C=831|o2
z<b*-}VI{DH(8GnkoFyj=fiyr(C!i{$R!?EdL+%q8(2+g{RjJ?;sglRs(11>97Gx5J
zvLwrOcwSJyN7?v=`6H2~(2~gHSWCjo$dK`#69#{1d}JN~70OPb%uHKlYX3r+ZgSNN
zPXy3WRzX!a6rZa~kne<4zHVUuZcESwz7h%q3YvT&X9EmhgAJ;M0bd0W-8oB~D+56s
zyuU-Ktt6sU68o`_)nD~9#LzW<ub`pchDc0F?x$Y%4k3pdd7v3eoW9T<8Y`JUh6Z~D
z4;+K-*O77lAbF>zKEE4R&{&;y`0ENjm;<rq=imjJ1Z3umXL?G>mcSA6w<<arcc~Ba
zY~eqgA-q^EY_ubfyAaUOk5Wc8o`qUN?XEubY^`f15vn6BXV($k)!m5h7{~VlAiCm7
z36ld?!(T&Sd>ozwO5B<=sRy0|JklG*4Vk2)xPxc6;iuv_v-PaazG!n#94FQM|HpB#
z*4be|a$*$AX-zRKcLZ>1_(&X<`zRjEZBF0~L~j={5}HumKT(35y~qfwISFW0PYl&1
zz*{gNp@sf--1so-CnC3UWarBn-vim1Eob+C)7i+*U41sP^M$TNa<>q}Rw2HdsN*{f
z<qIT!wl8N~8KecSRF_v1UVA|8-}HaMYaCx4B)N@s>_m>Q>U^OwP;&;n28Z$-@A4ce
zdXATUgwZ;4Vi@gdi4DS~*Cl3b^A)H#vr{Cto1})j)CE;KImQyi;v%bIGRI<K_B^X0
z9*d!JORxcIem>_XW3ej&|1YrE_?#FPn{STCVvd~iVzHCGvDjmMVzI(_EY@YxvDj^W
zVzHO9qgX6}W4FYtUu8>*WKuU6V>s+){5s3rjl=Rx>LYX!5xj{mWtzMhA4=vq&O&$j
zSV^miA+fO&yOG$wUGYflz|uG*_QBF968mo-NbKMi9f`fpvxQMPNT=ms@{<rD+57+|
z)m?5>m+k7d`I!GU+I(<I&c_=484Y5EVOqJx2$XVZRltgB_D2y#4Jse3PQ-#_V6+|9
z>y9`d8k*y#93w9Bl3KF7OuNr*G6>W6M5|`jo84$BoaZ(f&KZl5s+v3wvLMm;y(sUZ
zE0aY_V@fMyU^txYJ<ah|HgG(XO_0^C{0SktU0;YSA_~)wh^`~JYI}-H;!r8Kc{&$-
z-LNu@z$u&sN!fqDH71)Gn`33uOoru`qN2?m&<wZoJa0hSvS}uY79~tSExJwztNxcp
zoVyL&LMO}z;1g`X>;H(2!IdbofmT>~E8f=}pInQr__N-Mqf@aJ$DtKJimo3~;q3fF
z(dXSCZw5JnIgARkK6a1}vyX6*UDiDs(Z;Nt8g0ys9*tQLYs?AJbt1SW{#LY>Urt8-
z#l|^4?Dltv_KWR%rB~4^H6ScpS78U{wX`KL|CNN`ac^bRUtzZ|L^y!`C>F1>`OG_{
z_7-o}Ztuts0;<IFI-uN*rEP)fx~sw*c)n79z64zu^2Y(CZNW{F;x=lv__vGxo}s^|
z>F+7}dy@Wk(%+6+3M3P-7w4eP37LvWu9~H-$7|1$XQj;LYBjm2G;}hmRg=1yWD8~c
zgW2kb4Y8xL8W2}*cCfdSOHcU=TK4B+=%l%7&Qe=2jb9&~oUa#ciPM3JY!@~RTnHE;
zOW07|=C-F<07YxHx!GBP=Y$PrVPj+ULEFFvS7$IuQl@E<t-{7?R|jH&S}2OoLfI0n
zk2awYZ=r^WsVba^h?#xr4c%_@h8CxIL(9^=q1pBxKKuM^mCettVxq;%R%QD$+Wok|
z4#T2<Wg%C0_7*%Wc|$WB{Gn;hKzX>X732~`x8^EInOC34rgivSt-_<|FojJ!jnAYS
zAU|R;aMb|WWef~ubi0#@?s3ia$i52YA^5f?P_o)ku<XGD!7-9Nxee8T0vF@m0X(UD
z9;DNI5XbX~Z9ax%Hq*kLFHv1h>WjDHqZ=5}*%n^`ze*-2FJrvsPsk%LzsuGf&t(}6
z>PwJas{@kHjy*w|CY-}%#%~beE2d{1g`?G<QU6MMz?IdKGi3P$Nx8wsMCe=9(gbFN
zpXG^U&SrNFS(Xy{n$^HyFkihLj_H1ENqu6(>}FvcH;H}jx^~%IG@q*e(ju9^VAFnt
z5n7tVM{U_v=s?Blc4Y*v!Uxyl(}6Lv%Yb~%9Op{qdgH&1BD7s{!^ry0^`h1sZTiyx
zR`NTAdFZH%q8h{RGO1@&<FnR0$7?=i)a#c5a&ODp?`5auwKijLv{X>H`2~T-MoBD|
z6zfD}nUur^DI{o)xxXL<w$UhaueZymoJEaXD%eejvLYRa%&V0OYEix?JGY_M&;GvT
z67M?c>`rq+I~dR`8R+hMxcrFCh>@3Js`p>TMN<WfQ3Ny6fZayD-4CHq`z1(W&FsJ<
zbs#LSU_Z_A8s?`wO~EhwG)LPd6*NM>-o(OYAkv|0+Nkxvz+x!V+>OrL?togNUJb{D
z2fo&a96Uw${9UkjE0>E-uD=Gg*M_Li9Mv>sR+@T6Bq~4J@pn8ePDy4|qR0WjUb*ND
zEO85~i>%r-2D3cU5j#)>_3mwP+EV%C#ZO&m;BE<c(cm~fOOD!1B3FhWikb3&rm3U5
z%eF<5yrFq#43U1S^8s{i!;<D*f-<CkLiZwaUz2u6PhX_y!+mmjI*!zSNuKlt>SCYh
zqT4DU)d#EZ&@q_!MK@mrV=2*;o#QO^vm4QB$<Gdm%p{?Y^xcR;*!vjlS(#U3LA{}g
z^YJ=yxSP$jkVAtnO|<t%)`-Q27P<xZ9uM$U7y4Ke5qCrp3`YkR&3#?XaJTQ>x<0++
zV*AP(SKjAJdgvCDVcC4Cpi@%Dt+)l(RlEG{2&uZuDlOIkts|i<PfE~&E_w3bE<`Ee
zC{&x*VC89zzzCqsGm^Lo9Z^KAo7zYMT0ag40!xNc6JRDUQbRR3IpoCg&+sUx8+7%g
zvsY<69Y9Tmc0TM%s=pc9c|T3exTc%2ou}k4p`9AEb3eB8jG@h)0^7#}%}vn)*U6LH
zFTmzr7T4V2eKZ%P{1@>CpMkC>nl)<iC)KeQAF1x6#d>4O{-d)C7|S}IOZ{`KV8jni
zzsi-Ah)us7eM=}w$<*{3bQxXUVMVvmz}Ddj2rcgikM>BaTBO%Br@IN_zYz;GaA9m;
zHbpn<%ht!f^j4p?`jiQOpKCJU2*K#gWLJOlE-oawC@awryjr=Tzbc@AOa49#j;1WT
zzONR;P_*wE-ZJu}=FUpwTd)?rZE68wCQJMV@(#6Yj(OvO?yc{&lh8DV-w57G^GH3u
zbZ%cghHC79t45`cHyX~u(fU0btcdqvJA4Djk#c7u?DJ*h^)r~j>cYtJpwIEs(2Fee
zu)1ZA2_r%}Wi3}w7+zWww9jCvt#qmVy@4x}XU<@jR>^8W>rB@kL2iWfwrMt_Q1%)f
z*)SNBc$ZsIk)9GRk(8Oy!I^ahA)SG4Ws#Ahz(&m4G<}f%d=j~&qrrPHzvghkIAYa-
zME;%y?-2=mgqfz)!6zw{<x{LCX!y(wXlUh2Z2V^RMK)e)kNDUjm`0#UwsHR^2Uvae
z0jt}5fV|g4k^$4_V_nekn%O2pcmynDx9c0B3|;VP_2CF|miY!$NNrG`JC0UR+@)|}
z88Ev5^$95D4OyCH>x1x=?q!M1Qb{!*nCfN2>T+!`V3~Ef{RhYk648TbKGhKUxcXZw
zxfw2K>gB$iS*q&XHk%O`#@orTTX1)|*|(T^(?A@c)t%_kcbl%K7eGC|BGS{#jo#VC
z)^!j4(eFK3U%5jI)V3O%C~Wvt#2^}iXOFP?(4b}J;+@5%Evw)MhPWz&X2+p9`el&Q
z_}N>zMI&me$sMMkwRmT4#3px`gHXVxmg4P3K?k%@k~NGg_~kpe;V`zM+5wO5P<jLz
zFX+)GnB3x>M$SO%uHU^kb-R9NmWJ|qb{qZ{-($}%jy|ldhsKh<Ym<5u-RjiG6d`*?
zTg;ss@9L2Va}4Qx?*RSs&(oTvDZj^n?0!Ji1wVvVSB6%<9a>!_pSoUnVjhM>KjpJv
zNOZgjQm)tJj)B7CZc@3?h6s;OM7!962N#L*8JF<*74#M?6y+{>yV!)vwzn8Pt{UNq
z!DMiqv@I3*C33vuX1;%)W{Lk6GaAwT(cMueWIY_yT!fxy&T>IEl6yNlhgTY*?62@E
zcbSB;^`yViktRI#H16x#>59|%lc9__FO;ASAgnQa%!kz1H=tgQzfzsKnXB26-I(Hk
zTYW%JI7~#Dze)}13EOZBk&iiXGYtkXBO<GDhwl9<bjPRe(CTJ)=)qm?P_Ti!pJO%L
z16^Zvc!b;B$bI#QtV(wV)YJc*&v*<eMOnQbF(r3HCz+eQ1>4BmjRzQv#GAazq$W6t
zCwa|h@JBLtz~p+(UGnOc#*k-EU?@Cj!C~_1N(1_I^EarYHgMm_Qi1<%$=r$YQ)k6r
zVWLWA&fk08Yht3auS5Y8)yB<HR*+61VC3-f@soVI9$XpcHt>$xo_u^aJ})`4j$i5`
zD{}7`qk-e62O5BgY5NX2wl>snLkbJ`>|!ok{0c7x31PGMLP<6F=4xIBXM9b6NuJTJ
zEv%oa83wpROU>|frC~tb)T{7qy1RVoG}u3X#R76ozNE}(kK+A?rQ2E3%30*gGvK^i
zD81UqH7Xa8_-!<u9qC&PHe@drS^Va&b5aCfifgB!JWf5Bd<zt&)*}Hn@jI@CvWPiG
zA~dC(z=RUaAB7x)slnCDL`lix4KBX>%Hkf4&KwZe;Doaq+sGU{{U#EHx;`i>kMv_{
zTl^@@=qlU4rU;{sYk@`h(-pW&Z1Wh5f!pxM#?`7c$KOX2>*A3Lj-h{cC`yN!#J0;{
z>qqj@e93%8HFq`YF^Cl&+ywS=j}6ZGQQX@7el+~?Bx?0uZM#?AW^<h?D)q2$yzFy{
zO`bySo8;00gYl;;Fre7yvK5ERP?`(qTB~!rY$fe`Dv{nA-Y8d}K!qiDgXZ|_NF%9_
z;WfD*M^Pew&}{@Y;1V`eNueuTh`ST8%G}5R#NJkXw!u<zMGxlxI(ai|&d@=a7)he+
zQoY61MH=$8`J%Q>DmjHpe!>$H25?9UpkbfmZYQc~;2rupVyel#A2865kn?1IGM#<b
zqNL4$(pz(B5s>oIwGu23n6pTlyf)Dg7ywK&oJq+}?4TKDFF!h^9ZNq)Wu%HbVvL;)
z8N13p?=;A%zdfq9wO6f5!~h@ae_!Luz-Yy*=H7=jRb>K66GZv_h3a^ovH(+75TmMD
zrvj(hJ}m`xY=Q;A0au{fbwLNLoYRJ2#wJvhQNKH^X+Ra6#$A{qDbv!W&@|Y@W>V=7
zO>>5(*&;(>S)Hz*=EMT^(`@)7g=Rvofh99jOoI)E?rVA8p(5TPF5^QtQzWWwpPE8N
z**?_Y+M#|j2#&CV6b*KhT;MeDktwi8V+C{hZrjup;PUt2t<taThM-l{43SaV4BN^i
ziJ8h_7)`>4>WB>^WHUR(YzbQWn<FO89JSvmYgyy#iyuNRjJ2|TxfNmVW;*x(qwP)L
zo2s(F;iT!3ZoDECi-1%uTB&HYs4Xd)Kq4=a2nuLb&}yN=h|0i=pt7_km8Q=qF5@!e
zf-}zinQ>Gcl}&{%(uK7YfdZoxM7{Be7LYD1ecw6fzPzLXoSFakef)kj_uYHmeRn<g
zoO91owN>5BDqx{_Z&sz)iv3G|I4{3)et%ngu~HPFvZbG@YR4@ZR=b=jC5zt0(EM1V
zMVY`J{gkME<(ewDf$UIrtJ_&BE?RY?*P;Sn?LvtB;hw&{_@kTI&&Z0ZOSZCWsmS0l
zvnxI&g5|dyY>8ep-8RbBE<G*cgO(OnV>-g`Mo7;(m+M(KbA6;1V@S`s(a5y|_oBl6
zOp%0$K>0Mg<)f(dW|tXiFWbrOK2ncUNke(vorvpOP{eo@`~4@ni1EI6$svzDf7)v#
z(OafT^zblP4(B^iyBL+n6-ZsVzDE@*{%qiClpiFLLh2ykO)d3OHU`(Z%vAy#I<*qu
z&^v{<Z77m#?uV<3+$LXBW|hgHB)Met=rjXBY#rA8C7x_}`C@!|AjS8I<X4=gmuv8)
zjxqUVQV#p#W4+D)_{+K53=GPVc+@6pLCmn#UUh{7iH6BpR{UeP$t^Dbl;sZJVZrwi
zT|ZA-WBbfN`qT}gf6+<@$_HPK=h(BYuxS1TPGS9*V}$k3nyrU`>O3f55c^H2$BQzZ
z{*6%da#ArA)trolFBHj}Dp~|zAq<<^B>2Xg`R{S<`Cr=apS9oLX}=}$^jpyvzYHJr
zc1;(aM<2g}507JSmrd|pi!@u;Si!fAh-!!68%jCj1m6HkOp<1MyNU$gc6z!;y3pJ8
zfZ&U#rxL-3N*Tdnv50Boo!)xX1eLB4W!OFy`=!f;4e;T0-zCqx;B}y!(PZ9`Q@f)p
z*iNoNCTC3yevkKX@(>@;sln}N(9^jw7rF<yKv;Jq*(qOaxAfTL26QmLM6lQ3CjcbP
zjGZ6{{1y8{HH(6Mh`ldzr<;QZ4QEvqep9-_A*{ZwOAJgQCroy?T*uVE)BKX3Z&=!d
z^8{PfEnV<r$EQl9-=<Xu3!ssZw}A93%U~yZw_35lCLhPULjToBqi;Zs{NPL)<Qc-V
zaVAVWw0NhtYnI^qg+}^OSSyrxOz^!%i8)e<w`-o@OF$&m<>AUW-mV2g#YU)T%@x?B
z+to%ObNWhV^oIgiy<N=NEbi!5GI?09A%$twe<ew6wCDE)qEW2@sAD;sVN2VCi>PgA
zi-toYtijt4+}bvZ_~Gy!d7?P>_Ii7kE7umd{aB?v%dJeZ(cLhX*&WG>a<fyu35Sv$
z`<pgkg6}{a85&q^9UC*HxKe+D65~$@jbiX_oK+Yk9>qhRoF%R?l)>~@Fe%Z^7mYFs
zS4qm;cA6xtWgz;ro&!m%l;9RN+|s2ChB<?ubjpi&mq0q!A>Yy!226EVR6k*McD7>Y
z%uv54iC1fK$ki}6Pnc~aHBpjn-tRgcmQxO4!YMnT)nfEJ8h8^6#m?42O_yy#@3=OM
z1Gl}RX-OOg$I8SAf7MRdo}EWsa&=%#uD7dTvB_qtM|WbIl^rg!QVUdIEje|fTIWa8
ziwXti(@1jv#4<}RH(~^M6$<N3xj_XUJ$-Ctryuf+)>?PH)-qeUis6D553>a}Od~D-
z?Q96Tdh$D#i^LGft#YRlZ`<$}B6Q+KIkln>%4A^-h4rV!k`4e}qo%JJjA19-Wp-e7
z(Z*eNWi4>Y^;(1rpi_3*ku^}8ZStA&A5fD(8U(|Irn#xEK1fqNPBly^BUL?W>_u96
z7M0%_Szcxt;W5(`;5`AxwKz;C1dfNjU~<+}q2d-~z;Md0#R)RcOTaZZ0_LS-qftu3
zha4IUWaX8<kQ*ZrNT$N@0JO`~vnG?s!Jac6D$V%;KvFyxppK>0g<gce`Y4kf?Bs?*
z6y3vxD&~9GCBSe4tXax{zp=gF$YmeG8&JPJuetzGiZrW2T|7gSkK2XSeZ;^YsyTPL
zf}7i@+eL4E3~T(WwhKHt@{|=7Em9W7aha)oH-PW3e|A#;VZ=VAMk~YU#|9@MTJl<z
zi$cxdH*5<zydAd`oE$)XKwG7YdiTy00R$)2a?KH6Ba4}YGG}}>k4_~~UbGU<TG)3*
z{7iyl@}|{2Djs5RU{&1Rjn-vT=)iL?)9&+t#TDg1qh>O-;Z|-km1!cAspcq?DOeCr
z=|=cFQ_6<Fv!zt{JKysbp<U(GvYTgnlHef3#}uU!P(hmVH2lpBJ^_fXDuGZ@;SHYt
zWlM}BJ$=xp9c2aYf#SkE%D+y}KOqFiVE%N$Hwu3NpI(Q*fJKMnFQCm!@ppmXy9j^b
z<hlTP2s}k`iQ0r!Tj=CrBW|E=c?sT4%_N0hTh$g;aze$Sf%ejI9&6;5dtrbvC&J?!
z{2O|dE;~Kkli}cKk&b|OdJv>1_&WTK4Ww+qv)3M2C^iR|qB*5}GPor4-XI&tfJj*Z
zD)nRJD;S&D&Q?W$k4z4vl;PU}yV#5%!CUx?nUW3uUIzQ8>~xNFDf}HF^@G2;LPZv@
z!5kc<RTaE|CyYVze+@&M5LC&>%hs4+#~D603-HfW{4*K<hz>M~=;23n3x-yD^&dKw
z<ohGuFXlU53-0OA;-+MCWvsbjp!@&G{R^06+hARnzLz{u^iNFFWy6_nIp~(#D9r59
zybikdrf80y3^q7iYa*{TxSy_+059{1kN-xFW8l3Q=y4nddVEIHba{|Y(sKrSy!WQ)
zK#zYnYk?k@n?r#fZ@B4y3G~>H&Ha10|F6TRx2Y8!o@{K1;TZbh#z0TSq%HLoMH$_V
z2C;i!%YS-|uC=zapJ35*b-3O_#mulBL_S&myU`}976Y*aQZM`!Cg`o6P~e+$!i<AM
zf<5`$Fi))nm^-6jC_a@2@WF9X5uzAf=Y#p?&!l$G`uqmW1uHqsk7sn4-uCNxYG8tq
zS>WJU7P#MpwxX6Hi4$TBi)M%=$AM}2?k18@v{R_kl=co=hF3~s-`>xsJ6>&|tAkVh
z7ha2gbx0*Qr5#A7Ts8fU{>Z_n{)J~AWi4(pXL;!+JUO$D8`L`GdG7q=jYGWzyqcx^
zS+WD!mVu(Ky;-Kh7|+d}whSaor5ogeoF~%Y1R25pjdo4)q%^zS0Qh-#uEAhw@CfqQ
z3}5vVg17CAe{6=5;2ocli5CF!_%v^c*??;RB?lo_*CF1tx4&K<t8OGV_8|(JD8=EP
z%j_JRVX#6Y((yQ%hH~mgwrYa`FM_JW3iju^r=iie%R$(JYTz~_TXP66WcFjwAU+FT
zcZ@*@Ad%oI`4$Ej{dU~yEo?JLGjEmqw*g1*mF|PD{&E+s=r{|O%PDO%MR_)KxNrfx
z0Uak>8l=nAdiZ>nG6?Tb1^`G5%;G>^XcO!zGz)f5dS@Zl(f<%sn^6R%b-_7mC~{n9
z8)C9EM#0|0=c>2<3MxcS)9y^uD5%?A5d)aOx_U5T9Q*oCYVBtBMi0i7qgbAIg=Rk_
zzo8EX>v^oGkS|yscfv1*>fI#D``C{s;ob2WVo7^P&C|f~-}Bed%^yuOATX3scGLjP
z%5I`EQhqy>r*_mBrGfsZ%pEmpg3nKAY;2nMV2|iG`NwC#nt?KJ+b`b67+5ubC*jXE
zOqikzjHEuyI3+ABN=Ccx*SX6L>{MK}0fj~+HS-0K9M8d(ne%X(7#L%OkH}-Q)JFeU
zbH$Ox1MTugc>VIL0jf*6%iEL*EePI>D78nGF7x*ENEf3|2w19t^ppZ*V>K4LsYo=X
z)S`uY>SPmOP@6eZx-hRr-ewPsiD7S|mKOzR`h|QKj8ChwXXREJl>uLH1<1)jqHcqa
zv-d{er!8lI0Rl*26&cCne8{uITQc+hbLdVx<)frywokMqd8WGy%%0JroV*UjUhHo4
zP30$5dX%n>M^P~>n%xcDRu^p#1?DP5tT4!J+VAoY!bp7Rl#RQ>U|D;)gt!)Kk&{vR
z!k-+iJ`%c5Mwv_*8p(xZUc!0`>@GZsF=xF84|szKaEv)NQvi7Gy9v%y)k*Pyjl!xl
z^3!fq5?H1MhNGF(0J1}NKs-F&)9XV9YiH?i9~0$nH38Kf|B6D=R>Y4f!m37g-&1t?
z`t3P1!p|WyD&sPvGA=WE>lb6l(J{%4T!BZ@Tz>Z&w9=OkVO)`ks6V;@71v3Y)Zkco
z9~uKXe5HOzB}tG9QTa22-TY_$$h_)Bez@0@>+vS(Tf{nm6s(?w?6pn`K^kX7i@wyi
zZiq1;{uqKMa)zs<`Yf?fY?ESw<A%$Eh9PX|^gc@%)W3!VK^g3mcl4&b^8(&&HUupK
z(fo~Q-MnKg<W<FH<?XMWnpI$zkHavHw95+o%<%yid>W%!1s*oy;2NJKY&_ACbHZUd
z0i$n+DH+KyO1RvLtr>t&`=6jG_!{pdH?uE3HyGTe?=Y-+7OUn(zYwQ@jwvPvuZQ|S
z8fME+$r36)Per|ISC&!mEl1-r*boy*wFDY7wQ_P+e&w_>NMbm|VrVHexCs7R;eW2R
z>Uia}751uc<x?)xkMOl=c=28~pLFcd@yOUf>?#bIYMc!32BfjgN~!nMaL&ehPu(I-
z@}6=EzPAyXopK32nG&~2h2B#$rE91W-cz%sB=4#D(xBibl9DVk&h{ib)s3c;F27@$
zGQa>Ww8tD$QtgI?S^18bALz9z;7zT*4#77aRw}Z7fWr%M9Zj@XJyM1vX!kV<6)(|7
zbF2a8mhn)Ske01o$QLV%s3xpmS!t_s7=f=C;0(B9$_hq$QeAS8ih~m8Ec)`2>pW&C
z2`(VELi)OJmbAC<F=<!f0%=EKk+gLkH{o|IGo7@?91EhKLTo8z$84&EWbhVJ83!f|
zQHiap6<7yPT3P-i$y}B{)=~9?$7HWMWEWQD+PvE`?S4<S&D-;+G#mbwNMqpdQ&J&k
zXW`FCTrH+!tmBjx;j|FfOJ7_s@4+N#{?0smX+_y(Pqoq?9Yf57=^DvuB3=Rni;mo|
ztuV6~*AQyAWB6q>hCi81CiagB&tHbu%sw>M!uANhA4p!)0}R%`QOfq*wXyX4(BfO*
z?=0yuY`ah~D3)YCBVk5-o04z|!Wy;9?ZJ*DbPzDzpt+Fqsw0GoE8`3X7$|FS82Q<5
zU9XjR$i?mg`5S9xz7pqEb39k#Ql5znzMTfcDQ~ssd~Y{(u<uWXx`tg*1cK3^0kERl
zDy-V>5bQrFlWp>s@DlWN5>;;mrrZ^9r&%itO=7^|6HVVBH@5)T!IkJWlUz}J)=f9W
zzCVz&k*)YeN&h0@ONn;mUtsA0I;iCCOO4b=YgMjwR4438T<rMmgIE)pm6(pAE$na%
z(uUzl@v|EC!<JbqM^cNp=AG#qY7yCZ!<M{KYZ1D##=iaE_3PU%UdtB-y<cy1NA~NP
zd7*x7gK>1>+CU!<UUi0)=v5s;#RJC3UQN<2R~P90sgE_y5KVij1(9E#MPHWC7}%!P
zv&;3bdb>t=7WS?~?*i&ZN7ON_OG8VZNqPI}h%IpV(9?h`YXQp`gM^|gR4huQwKxZ<
zoT|xL1-=%^WS6`0r?m;b8_>&w>~7Tr8^Oy@B9D;cL2Gj870n-Y%@gO4+mycAj4kL2
zwU&ndu3n%LS&uC+9o0vlcmM3spV7%jAE_p794_vP9DVa#8vW&Kc;)T1tD#ep;FNa(
zKe%F*b^(W@08@kB1MT@h2zlTHnytmqwpYgH3G0b4N|jF<Vb!smR@7&jqo#jmMs{b)
za%gHJN|-jQ&9qg6x1o;3XBsw1v6+rzaIa!SvVgq^M0hR+Q9;%5;2F|8>6s(={>72O
zJi)h(=XnI*r#x?g;7h_F--ym`qzd40iOsS!2o(YN7ETQ9R9noRN}MD><(uLWakv@M
z4#6+rLH4W&eVuv@gWn-cXm^>~Y-NR6_j$}t^-Fstu*4Vwz}*h?WF~v(S`>*CW=-S0
z5h|ABHvDv&+w^U{<5Xr4Rj43cIKuOY!*@iQX{$=fD(jvm-4B`eQNej}_Npc&-chyH
zUKMn}^A!$Z)h=lIRyWWO@=ou;1ZNF1IbxKSLvGW~G3Lo}?Ep6SYXGSM&>DnkBbchj
zmP5S!x+BUEirrAf;4c&dP%j81Bnqbq6(!{KepH>{yBK)k;LEi0ay$caj^a%4aPlE9
zQB`|!%17ep%=D_co=J97&?#@hi5|%sPK1^fioQ@m4n<!<gSLF2z{Th21HPE-1%O4R
zq?~sAjwdFj)wcdIT!N_bhqw8@Z|Gviv&^?&sQ3)8Yv9{<q!MTHdeTjyHIZ!M2#Q%A
z({?MM5R-PiR2;@j5H(*2OvMUozBdKawc2=3B0CXtf-2BXuz+X`_D9)W4x?h+7D-O|
zE7%q<d~7gm$mKl&N}-Vaq;R)180OjZGq3Mozed!@dn|Z=j=?V5EFFtXwwP~h>lYwJ
zfPVLcRpHgg*OE<`a9lCLq6B;c=km1WHo8cmmJA@VAhab8+Jfc$6&faeeEZnsjgA}z
zM}&OtnCNvnuUY!9A$2u=ShpMa2`t%hgL(RS)x?E>fm4O&cGGE@>B+&pB~&yJ0n{Mx
z0UFliJ(M7<cg3{}>+P^B8il{?5WIiWrWo-A7lr{@B|VIDWUXBE8hh#zl6>~1cBpWO
zGyic_t#rJ;2E)`MrT`@N8dB<X*ah5`u97n3HCe^SM0+i_3#+G9Vy8>(7~r@%28k~|
z06~?wy3iw>>`qf1(PoJ}$7$M+ZwB|FG?I_ml~ehcUJx47k3I?^C$g0S%F5Cz_`16_
z<R?`89(CM|KzZYc5n2&^===#gnh1?D1Rq6+N<*+TMqAZuLv)<obP$M<d?hKIjkHKS
zQYZ?Hc#cqU5)c{Rf}Ui&YN^^A`~k9z`s5WIi&$B~%_!jsj9ev}I+Rf`%$>k)4R|C(
zQJ0D}GnU25+pNO+1MI@tI69tX@+t2R2cX>h3$cHva2W)~H4}LUq?`-Ij3(foJj?h>
zinHhZDBVE&4=Ft)5WYGZ+Va6!=z>>Un5n?5V9bG)+TcMAFlJW4hd#8#&Tp{J;4$R=
zrL3vAyZ0KRceiu{?rbTScJ+*h^<Di3zC<7M^r0P%D+@NXA_B({7->+fTGt274t4z@
zI47s_p4YUYN3*GH091!xmQP{te>f010EDUn(oM}qKvyvOQ}w>HP~Yvm@2%~6-^Z(G
z^?mM#)OY+0^<9eZ{06I&ITpOdCfKPzD>_4`=}_1PbFi^W9IXl<8I}VYL??NUWx#%G
z<Snc-B_o{1ck`uK;Lc4LqJnO9NQhxK%+4wRW{x;F<w0cHfB^#Xz?g4#3HG)~rZ83S
z3Z0jsxA5G2)}Ue5rXk#Y6!P(3|AJC{U=nr6Mvv2mnk9G&4ihR`I`BYTm}LYqy}l#F
zv?7bT4xWNjk8-lf_m2)-ihRRa;aCX1-oeFkNyv~kva2AE0Luw*x3Y(xJp2h>O2(H;
zJMgjtXbm8@=nkn>zeC!Hk#@8@q^ENE9a1xQyH^%<hx9+lZ(gteFXT7R4nM#AW_rVa
zFTd#}v$=QX|17haedYONHrxLBzsqd?G3)<cX7l6Mkun=cUPpw`20!VaoHd7pHZJ)f
z3T=wT2FD5%+5o>=2^iH=X2T012@X=ki(GnBzDyUHJX*%3Hy@{+Exp<I9Z7G-{eo^+
zQ`@rs|B&8vJg+z9+a+9jlbuG=n`V^W^t%q&_V~Oed!+<)C~iD?FN&scTl_4b<YpF0
zZpO`wl-v+yf}3=3Z^_LiGr8mjsgcdh^d6pSCxlgfn3)$GMI2F#b2=w)fAH@lxykP%
zRNMfm{M(FzZ)hJ}s!$vywedl+2*cglWkvK)tFD}yHMy7M=3Py4a|}?x#qjNh8FVFs
zx4&Zp_V*38!0jlxnP?Bp{{KR9vzSY6>QQpz{;wrBE-tx&?%At6KBUFOD9O#rD9O!h
zQIeZmG|A0vA<50rXvxiOA<50r|0KC_|3}G<`!yHf!8x|7y(qc4z*aVDl9UL4b=eKj
z`NQ$b+U|b@mg<>DrQgF}lHHuqWH-e`(UR=uNA~mCvYTIK^pf4oM%m3Hja+t9OtPD`
zy=6D(pnzJ%1vpKJ<xu1u65y1i7!1B9&je~n3jCdiu|Cf{g_zQ@QnN8Nlfg22n+lIO
z4Y>O?NLGuMTH)!DDa1Wb#P2AHuF%U+*JHx-b`lQVlt|AdK=l)^cV@ca8=i>5p*#5V
zLyI4Wzq6zw92}vdFcv8a<n`owXye70cEQo`ngd&IreJDxtZD);SSYk%A`Va3PkH-k
z0|oCuQPmgIL&Bsi6edm1vIa9rn6wvZw^U-t;k?iVN!chGDyFkMEJ!+pg0<UE(vIOn
z&E-g<28>5~fh%ibNR-58J&5Mh%6+}re@*=2$|~wD0H_QJ09NP%fGkaj^8yzup#We<
zH?kUy{QE57K|&aB!GJWMN368Amssh&uvlqn_iu`oD!DWz+?ZE2Sx~GdQDRjOXxC`y
zXd?6p$@SlN&!N@$FN5B_W0CR=<qf=y!%3ghOQ!T-FPYNh-;gO)_WIr~lBY<wXy1CK
zD^jMk_?$AOvqs?ssDr#MP0@I@!`?-e#*k2{?ix*~^fxY4ni(ZjvgglWg72+vk}9=@
zq)N|qbE(o57=`@Hb$qEDLZOmfKB)91!P_ymr}HeKQW!KOR5}(O^nIcJ^MMa_pt*w&
z`slDwX;EjSQ0e3Qe_N<D>0Mo@)P!^Mu^AoUxR9@!LwD5@Oh0tpUYVlHmGYR;kkhKB
zKiq+HHRUyEWwRzv3R+In_mWJ<@a6|TCZg`lbElCXt2!3!BuUYkIfCyn#}@Mh-&Z`(
zBlr&Syaj^qVv-&KLLC8T8@gGkSbaXZ(J7L*uL+X8+w(4bj!TM;xlAp9x9@WyHRDNj
zs<kLBnoN%oBBMu#B6SFPA8SG*TmD6!Bzx6yw`nKJjL@%ZRSZgH-X-DD&cYF%ryRgq
zK5nZ@8nsz^46>+B`>3P3{AioK>Zk*ruY$v3H}qgzl>F#;r2OdU+^_kO^BY2>ye3E0
zQF2GaMM>@F79|DIQLH0BN$_2D28RV|4f=FB(lMNlC^^zT8c)KwBu9c3q!T)Mz2rzo
zG&$0D5ptv=K3o6b>xb~ACPxxUj`RhWBLT)IY=1gJj<naL@#17O$m_{7g;vLvAvx0V
zsTxfq_yQ*FB&tAJBypWjhU7?jChe4=p1=mvJMrkR5hy;GK{BN9F3@C1?ss$<QWMFL
z(29hgKqNytq21X<*2R0P$&f^w<pj!*zP5cqG9)vXA>nF-m56eqcV%MLr@h8KJlJxY
zVRpjOqhe@9!*bGz9myeF@{DRa&`s`~HhYznHCZ3xbU;s{Tw}{=MCDW*o27&{fqbx;
zs9rYzcyEbP4?o6Bq_Mc^A|*<PHHp%2+;2j~u5(G0Y{L3_O`!CtHrE_SpcH`oYmJQM
z0+&ZM$<lImi4c}76_aG?+hZtMdM6}VY6f(+ywvX~qw_A{E(=MPa^4}L;5C#imEnMd
zBuji?kVJ$b2_E6XC2c@SxKs`KL_sEMel$$2_<nFBY2itsp>;&8fD1yS?|d6WNNA`K
z>m@nUVUjaZUyBj3qEy7x>L=j{5<%lTF>*(Tlm=}y2S;~m-%-}vE6u^HwMQ2c^hkWd
z!bG70<Zvm|WL?UHia~sXYEq_s!6w4LA(HTKM<dtq`{$4{t-L2h{`ZnHeROv(DO1yV
zq)bJTQYKtvac~rINlS4^%5-U1$~69LDU<mvZRk+S<oeH2rpUdIQl>K@DU({H@A*Gy
z)aQSYGJSe?sO#sDGOa8MOPQK}L&{XdrA%7i+d_TUq)eCprj#k=P3k+BGP!z5nGi>!
zl&OaL)2T0~6m(PoCo48MsQjue94yS268y&CQOGKV-yr<v_W_zDaEmeDEDHAaNM`i;
zU&5z-JP^u6CgXXzHzY55J(>35AoQyAz%PiF`si?t+|~vqM*Yfe8YWbn0D1x#J)ql_
z4rG#%B|eCfqMzC|NzqPCQiNM(z#D+-Y9RyA+~RoH0f~yqUYW=qFAMqW2);{lBo!k@
z@^=pX2`PAD-~v#_ZY{&9JIuiy@=qbD?PbB=qco}Vvf!+cC<<|PBYWphnkdTIfi%xo
zbSgUJVDP36uKwk@f}L2R3jhv1h9f?Mkvu9yq#-e<WZ-7bYsz<1nWHTnrZ_CBY98{N
zqN-kgMY^GPjbEU`RhKH=Ey{7G{xOENF@#=?o<yr}4F-eqBw4yWLprR*M(INK`WG6}
zEy}IzS$x-0CtVEnlwlgpC9nC3*u*3?Jrz=Ns}h6Z_t+<&Bh_+1vskyGGl)R?OZ`ZX
zag+Pu{Y7Ky?E}q*iv1EgSGar$SHRT`_rh`_RD(lNk>+Cp143u8-HW-GiVqftbk&08
zNHjDpm|+<*5WX^R8oGXwkE1UUS76vhkcFN_9#+v@{#Q6M^3C3ZM(IZQ_HgLQGR*C;
z=bMdEGRi4Pu}<m3Zqw`cZdak;Z(pFEoid0`(Hby92VfUHN`2sJePs1^|9!gcpe@5I
z%6p|tAz?<FLuVR`&r;R$l8nw%6nBmyex;#z`mjIY=$IqT->K@F+(GcIlR)^3aFWPz
z)tf?+vdoJNhCEnj?JoJpOBQC?VXz$Nup+NI_#N7LRkV19vNsp0>LvN?@kRRbD|v*A
z!fNPx^OQ^eRt&`b*cT%$pN4t+VJIp@lIYeAqi3s<gnSHiQ*4dtur~Kt%X(rkh*S@(
zAK;%mn^zd6KiWF%f;m%4&1;c2lNQ~ESIA+;!rybcZWEdL$5r%2P083c08cQN0cDTw
zr;DD$J4V#2n+hCewSge-1;~QC`(sO-a*80lT&JdA803|qnGmd^L=x#>#CA137K&g5
zV5+}H{7RIao8Pwx6-aGXCFfO_btefgSL0P9eBfjnDQ&jdybCX!Xpk_h`-QGbSJneu
z3R{V0N8agBgbgcJoM_~ED4x(3FzfYVPMy=VQA7{u&%JNthUV5#EUKMM`XiZx0_TZk
zYMnAzqZ8zv1j#0a0Fd*>SPuF0Hy5w!reesLVy@9oJ29`N!Jd^JXP~My?^^qUcwV*P
zc-*EDS`&Fp2<Y4pGP6o)M4OBv>HrR+L;g}tuVs7?F%E}(%9eA+v=HrHre_ri&&DBH
zzA($hwPm!J<Doj6gcw6Tv@4ZXN3V)`-_-O{t?qgQ)l*~?o<+_ZjfvC-<w;yKv~prR
z7YnORfMH9$T}h>AV`39t{t_LOqrVXszvFO4fR7SZ<Dgysy1`It0dUVl-i8>w|G314
zs%F5eZz;uBt<0o@z$fv;)D^kTmb1}jsh@Xw5Oun%fbmZ6M$@1M=*%HorCo5rvOHvK
zuqTkI9&Bh=u$xxa@E;Jx1%HasauvRkKkLKwhH2r$N=A2@dHdb+ceLU*<GF!ra0u`K
zfk~-$G}{papJv9RCuQKPwdP!bcOOpm%fcj2a^(P<+90a+=tEDfsf@>DdAHinV3@m2
zykshd^r*o1?Q*?6r<&g?1XA<*&`j5$iIg4XnlvF=0HQx2yqdoKdp<3IK~xhO=<<rh
zoOZFq4v&_4;n|cJl6b76(WJ%+PowZLcpQZ>W#~1dHPk5)U<vSi-MY>utcHGhzl^~N
z_6Vi^0XCR0v`9j`K>T0okEh4-?jG!6-T8ZXd+LAg;gRon58XZI@8NLc**&yi5BnOm
ze&GRYTa#+wyBQeraF4F(-J=v(ZARXeTKca=fEUY8u7K~(t&iN%xSQwXG*aLs+{aVk
z3v$ic;2DGA^_<$gmK|MoxiQpYzPZnQhojJiUHTL5{|-JFd@Gi069X@&BD6zpfC0))
z;RA#)#e7`k6tg|2UQOS1m=IR7321n`rDw?+#jK_m9mW-nkLfn~H19Cr0h^^qc-llh
z3}KAXtt@^T@a$CYsW{JgcKv+)+`AQ${*2(7ZuGvDNYyD#I4dIH;7n?QL$0Tg4U-P@
znfdrM*{+Fs`;~X$T*!tRf(uAW#b^29Wb8z24V?^DDcM%G$Z(P1yAD9uXOH8T2jiEU
zAE!e&I2!VEU_GD)d;pI@B#c(~=Y&z^AR>(L*o?lzOAFB!A-f(M9-fDQQ1YrsWz6CA
zGx2eq%6Ohr8S^ZoKTZNF<6%)g;F5oF)|^4FSDz$Bc)g1K8K{g5qB0yn4}JLwP#KuZ
z9fkF^<B%X{-w(xtt;zs)$fFNwr-wWJ+N=V=b)TYhTA(=&@?5qy4EVPhI?WMbc%#!D
zR*Vnr%F1N57xKZ)v<s{DYjJAFz#_hT7+SZMErjKX1Tc{Op*XSLFRl0LHXNaP@><w}
z|I3=zp=Mw(=4A|G&C?w(QJM(tA0yJ=@H8Ht!Ip@0Jx^myhv2%1^ang$hUq^?q+j7_
zGzbn>MWh$=G#U{H=SHMUc^dDZg7-wEZ{z8^Fg+$BeFINli0R=G>5F+f6YIPnA{|fZ
zylQG)sb!P!Vzqu}OJ5RJ?_~#H!~-74w)j%-srx-~G$h=a-o15Eu%G@HuqF-G|3AP^
z`z=_P+~g2e8J%j4EnwZ??HV{g*5<9w6jmjPYHh$qIVNvSCdOimvs-GU9rm0Wn_T^y
zK46cEVJEN`rc=Z}Pd3{zS0_@}KL}i(7Ry+7w~GF(mnaXTmxG<e9xdo^@LbG;f3nG{
z_Qv=~&#yO$Ii13)l$$UxGdubM^mmnjX<%-?eSth60E&yXv`YJOTF;7|%<r(zayhHr
zlaIdsun}EEqN&bBEIr)z7-%~+mHW@DXps(*gUf$Ww90BqAL4d8A>SYw8d4$yCwatA
zM=-9;QSy$|;>g=yHY(E*c*5vF6U2QkIp{GvO$W*>nNnAo<pJrMGRs`)vNFqjVBenm
zz21zc9w0}nH^N@pU<5)$Ny$@f$-gM(4T(tTZH<9CZ1SEe6LQp_(cy<?S?J2)XLw&&
z-{|e0wB!ltciv#8=71rXER9o}q?^3K4CxAQFi9Hd?T+<SIp7_Nf8mh#c(<nok%1T1
z{gM!T9XbAv1}pa#@7-<<E+wb~Kn->@#CRm{4?W)RPJ6e<pxvkP2vp=Lrb;rEVsCJ=
z<n{*pNTcABSK_w^c)R0;3KW=jHW+|Y@Qk!2cPTeIU;>5%$`#g~=%ZYTPlGYaU@}t%
zYj7#h8bRqJtotElUTj{A5)02M(0%HAMG|uB=(ukcG)^&vh8B(`X_`@s;fhrKO>gmN
zhp&?DV4W!pT4ot7RG>mb_}%nEdY7nnIPw1Y24|Y#y*T*z1&SL$h7pKa1DPKNF#||n
zWjKIbKp+Vk$mBP83(|QD5&$eMvK0oQ;v|IwbY;zS$Xnoigi{!u!j+*A^W^LN?H|yp
z);Rj@eMZA2x0uNlmG^R>R!Y(TO=-5lf6HM1qZwM<gJO{sd39SgJZjFehuj|+Z)7>|
zk#|Hi*0+p4nU02d818_a<j1wdg-jw#;hb;pux5BYQ#!2~c!qdffpKTNU1{^v0u$2M
z>A&;)qm|U>^w;r4x&JXZ7e{51E#%TTj0Y8a9P7Z7rx}Xg&O{HJA{?s=)dsTMUW*>H
znEYoFT8Jg^H|{p9%AZ1xY^R{PrRL7*`+YR<fJjnv=xZt6?4JJgtwC{QcX`U7WI+X~
zytIp(YBuag{&`4OEH-EGDQ`&zy8DoZ0E>^kLOV&#rw}<8$~Jh%57x{A+g4z+>drRa
z?Da3gA9(pst#I_Y^QztGDGlgAv{=n%b2kvFgJs&w@QNMjNEgn}<g5%d;ZJim)MUoJ
zG*B^XFw4Tgy#90e6~c)HlC!uGdVCVSOE6L13%q@{7!aEQH?hW8MBC`d@S;a_oRct(
zi*@e6k@%iS2$#Igg}PRIkoZNch(S2V7&I4|B=umwt|swiw%odaE;=*ojexF@!OYBS
zVplhiv;MOpE>ENpbIXnBetYyeVh~35VITIPU6h02K6JBK>cf8M!=1DBK8&5p`*4*;
zziS*Yb!EE?cg4==uBf}t-8vT<T%t*Fzt!AH_`NFmIF2-H?pg)3@K~O&!#_WdR0M-L
zXTwWS6zr#-%UALg@?lSB>_p^7+u*1`<*9(X)gBm|jU&)4`lmIs6jNyK?t578_xO8x
zzaMu4XB~$DH4eIDx9E}`R`PG-vK%CbntOzCHK@1L;9z_jJ#Fy6+3#qzsk<;NM2D1D
zQN7?2)!n+lj29`D8A=+Qxy>+@B^$X7bgbN<^eLFuI;L{!<*M*zU0`evI@g}a9iq+C
z9ijok(au}42hZk{5jh-dfmmk00zULMA=(gYxWzVqhj=1#QGS*+c{#rK`8nPr+9($m
zyPIOF1Fjf@NBHR9sLMYchHnlzY<|L2Z!&nhdOVrT^gG>jHkozI3hRH}%}IFJ^7lsc
z4X<_C5;bIUe*PHuPITwoj@pNEx4WWA@cl$LUe%s-q(layYXTCrqZrUY+HQici4t(K
zqkPS2X@1<zrq4q4j9ZM*{-zQj_cF*Sp^N)(&*r@a%8KsvUp|A=*5jGYuAYT1z%Jzn
zI?J4eoS?FpT{w$^(3X`TO5AFfGquX<&QaMosI1;TRHb3MNc8roDBqn)^?LHymYLia
z>Xk5<+4TtoHH59288MiDpQ#V#D@8g#WIa#p`CjXQFCg}IPk5ZqzqS(Rz!zRS{Xeg*
z9go3##pil2T9>k$d!}f8Nbk_by8s>ZL+P9?V}+M{ky-%+6snJ|h>?=LjhTL1Mi5OX
zyp2Xkax?1$*7!6=92R98EEgj>YcQcxS$P~BTz93(jqIO5JNU<$@n}VlJS2VmBP2S;
zWjyV<A75zsoNvxTeAq}2J?JfKJ6rQP1;i8s*Vmph!ikRIn&Q}>KgSQWqNpF``!3Vq
zZ<GXAU`WqKpjoQMnzNoReA)xl&SM#=<x8^Axr=8Ie9bJSL#sN+Wf<VtB2R#Vy~dC2
z6x0c3U^P$#<`<Oy=nuf#mgcQaOB|QseaIZ)6I<%-6Pxv0fDNo{=IdRGRd*-`w)+iT
zhZSLdnNKjS8cdA3gH>}h8j7=dqRj~8z!hH^DE3aQ+6<MvN#{z0ek|^H@;g4^EAkmy
z-DWiC<^I&Q_~=hv$A3QKKcB<TIp!b3SFvB;0kR3spn+mw^ujjs{@|f@yuH(42+WI7
zu;5`{Fr5mXfr9nv{E7w}+6U#(l9@g2A=P7y+q|5gYp8)Nh~861K)Bn~o#)gdw#!Xo
zN$RRe_<>q`ff$P}-(kg8r#WldVqK<Mr=@!C4`R*1Skbfzy^F>ZP)uGm#&SvBd%%cs
z6pytauS?^+^l|uj9FcFt|6txc%3Go5og+>{F2)J&cH|uvy(eP@-z181deS8LoM@uc
z9V7VCk!|Tt7knv{7{HAWktZO(Ro-ran-+Py%~Bs=Or%6_cd}5Ch_?)nV&qC-pK$qi
zk-&aP=k3t)g4dSg4cIj>1RvgEG_ZSKL!XqstmU*y0C=+zeM!W<c!!as_pp(+pGHT2
z&vFoX2NXU5g#}N-z+u!?^wtFfCz@(ROY>aT>v3Kjg2kYtz7{TU6y<x&B6=nEkrOR%
zZLE?l`sc=CP0*&H_*ocwr%C%>V9aTl1HpR@Ko^XXz6zoPS(xz;-bN4NYK#=;Rnt8;
zc}GwD2GPRxTGXc0r_ohu`j+h!k<(-L$6X8M$^f>wgaRU{={DFqeiIMFD{uD(p+&C~
z%NHxn_68HBo4vt!sSj{s_u&G{404BvsV!gQiu|>iFGlJ}C6#Pb)34q^8{|U(Q62}_
zRRr4@hLws5E}p}^ig339+%p8XJOVDC;J#@lxQ7t#a)7&y;1)!{r4igJ4tEU{!^lW)
zB6oM#1KdHKGQz7Gr7W*%l7@QKROuqGDlCQ(>$^COO@2_v!L~az|My|~91fI;A@A9G
z4Bb%JpiuZX*w4uR6z6rwJG?)}@Ea6JNDty4H~uN|{%DdWd4G(RuJ-;IFAY#PN!PQz
z59pt+za1DEt1B?<iGS0Mp9?=hiaHh;_BhXa41Sb_@O&SCz6+mcvuEe>Aj~0`#&c<P
zE#s?n6Rgsm6JV9zO{=sfyh>})>o4o|2&?pNT%}E}zyoHtd{vf$F-+R_)7SRGPssaO
zv6_Bz8%5`I;w3j;Jf0?6<cKH97I2$b>#cx1D^$r0^N{i;uFz9vsjv4`vXtUI6)Pp6
ztT|h`$|bKy)ZikAD&D_g+5nKAes*~k;*o}D@Gp)6S5+y-!UDk8w;2uc4m^G3xFe7@
zHClEo8ZMS>qKG5UP}jZF8_fo_7I>jdIrU2bQg*Vt=aA#sENB^G%RBz0svD3Cw;b@K
z%CD@X9RRef5+`qlAIky2CeY+WXfl^Rxct;!%K<<x<h_ep89YPUKU2djKyGAk7&rzt
z0>^!x_!vN^r~YjpuICk7al(L%J_2OXhWo=Y%if!SY*3J52h1Ss<XH?mJI#%6{kR$5
z!r7NLiGlQQT{u~dYWk=xT5Z*y9QOPBwaq?x3;FdO2+w{KyJG_41}o2tgFNL{diT+-
zTnk|MWANJ2Aqmht^x=|7o6Nb%U?6cU#*f(p)%Rx?+#k}hU<r_vBi-`$c4KRuq+fKS
zt5>Qe?uYk`q0rOSl=DVbE?wFjbs#5m)O*q>R3sUBuwtv7T+NArg<00TBP8O$@4os9
z-kdOK&QxgsX2f<+dKO$Vo2YC4ic{Xk=DdPP9l)fifhk5*RzCD14h_Y$bGqb49h9ql
zQ?xltsBUSfv*d)P!oH2Ic#(@3co=$&EM!KKX<)2Im_EyUledKpfP(`a)WM^EEpcz1
zE#OeO6JS?KBYP&5M+Xj7(_%s)p0b7$r(t*L>^m21tuIhcZrSCTj4}pZDDSq(Th#QH
zCX~E%L1V9gYUEwah|Q!nO6hir@vK3vXreFv5xqoDH*qh~<irm72y|k1$>gkDVcpL|
zC|0?=+m122;k`Lc{A%Yyv|B(IIi}zwqLuuq-|acaIuFHHBc3$%&&P4qe!htgk-%~C
z%dMvWa}!^!mW{&Gf6xe=oUdJh60_5L+AKU>&et2u_yetWr{&9e6Da7qXsH#2G0o`7
z9uD19gn9V|vZL}05CeB+ik97T$6@Ybo-2*Qh@v=cfkN01=*tvNjMU>OcHOF`OZXg^
z0L@QO(;wo6>=+hq46W%%ZbOgdrpeV2mjrIjV1Z?PY2cQ-gxB(qV-ykoPxu1_T3ldk
zh7ylM9vxTutoS4_a7USH;{k?Bj^XevYC`1O2G8T^`H?0XG8h)&=|9m>bvbiRy8@5F
z@STC-L!J$FuJ*p94d2FjQ_eAbeGz8LGE&RHh?%J16B<M(mK7}HgII_WDaF9xE<zVK
zH&G88kK#B1hPZ~Fp?thqO}~^E{)Gy^!5Z1kwrC-kcF1Oi{h%Z5h|xvR@o9ButJ)Z0
zlNNG~-Z>53dya00AEgg^KZlmZsLkZ*+;U*<ktqEl{k`q{y~h9bJ@mh}A&G`J8Sr2u
z?<M(F-|kU*J4Dm|iE_<a_dOW0+ANuXa289%LebQiS6yB((Sh>mb;&VUdV7ZOa`o}L
zEv^z<A#7)t+~P9T*o4*A{tjVvQT(j|mmvd#GF>d+p5n5!%uSMQW>c*T=1j0**xQ<H
zhh_8JL-+-`Ci43*jzHAA0;z{eka%rkzvB+sE_LVBI5Es~6RTADqzbY%tA-8jZ1%Pp
zHE(QT-bXf;=TiW=9PfgRSkExonmHOpi&FaNOYo=9=u7b@VYI>ey3rHQ?mq??A*zl*
z>PN&s{ZMZ(Xhq0_$h9yyX|BN2gf5Z&0KVfVF;GR%z<0#i0f5+!^Tb{U_JKV!i_|VT
zXC(NZ?L`C?6aa-7`an$@Qa(S0o`LL%g>n-N1xZZqz=H;c7ri+89B3co?TYiHy8KSF
zEnq!OF4CMtOCzy$Ieq6nVsQK)<B#`=ShG;k0jYJl^mQ*MM{3htepgJFSbh>eyYPqd
zg69l|YWXvyAUr~Y7k|A-P2hb>dBHL$3}qws3qD8LFJN{G&qfih@??3zOA&b(3qdI@
zF9<~BVZ>VHj`D)%Bl1vtRk^9W;KhhM3`wmFFE98sCY*=P*pva~1)o5k+^`NCA=qpE
zR3^WKf5{KCO>Op5Pn0AaDA^ecIijD&CHPej>Ajtc9$15G%F^gbkQ;-U1Qxl+^VEXT
zPaZKEJaO#QgNQSRxQ^h~jT?bTg*;2QmRU0ACze?ZQXUX>SC&~W^(5w1SE#U^<St=V
zG8^*(sX~}ggG#ManhOCO5_kdRIWG1xRy{{-P$cfXqfh*VEL9wPoHt@a4e%GlW)H$8
z@Co#>a5P|$2E3^m^(+d+rkafOg&zXr!m+65S8-CqA2+(jNRkE^lp()|G`4>tzI|F&
z)iTTIz#OA=wOI0VC?q2*tO^+yOY_J*|1M-mll)}hGM?fWm(+_U7!8AGHB>qnKLGVT
zdkQEI@352_;xVr<G{p6RzL0L&IS#X2KzPyQ#^ZJpucua31Cu<OSGNfL2L}y^H~gtD
zRO6X{E}3Qc{F;sKMbUKHh6F4sUdVfG4MrmuP69jNkHKgkc}H;m`ojDjv97ulrVu7d
zW=erg#c0()vsala6GXK^Se1t*+t`ffX>$8T17M6#UxzdZNa^>jWj&dsPL}9-2VoNH
z$EgMdonz;9;KiBmTWAGdqs5h35<L|9^C;zSCXVK?WOn)sQ3c3sQ{!nu@|qL`Q)|jB
zQ>6}U@(sRCo*@+-QXere)>xK*sfTW)l5q$tI&dkr?+7Y=)h7IYe0{=HbUO@9#z^D1
zOr!}F9UgOGrZlcFQ5p*aa-*=mkySotpiT`!Nlz6nVCoybsC82z^?WG6JQ@SFE_gSg
zbNY@!11EPSB;0p;v$6m({ybt+{QxF?T-*Yg+7>t#<Xd1KauR{jvD@%=GL}RTqP(X*
zt^+cF5P6qE<qr945Q&}9kFO_U2S?W60ix>wszixI#nbZAO~<~V5Lt*7@WKKA4HYd%
z=S>w$P^-z?t<Jk$4D>%LT6WA8(WmbF&*(&ZzAF&h9<G;k0<>Bm!s|}zsDJclK+oG?
z%B$`EA@4z2G3g4tbjcs`p?V(KyH!n}wH|L~4)f0nzQ1!SwbWjaDPf$n7)V{_>CjU@
zp(1U*nA3{Cx#EsfKzQf1*aPE@z>h#yHs)ul=}Fay92hsW+sbxUgqV5BMm5mu_!_VZ
zxFIndoTe?deFG8j$;gcSr4x_$Uqy`4-0%f9tzJo{r?p>%Hv2@J@FKDb#tENEG10gW
z($Gm7ckf%qkoww&>;8OT#OpbDL^en%VkhA9IQv^9(!m(BYWkA(nov<qANd8cNUhGi
z{c3vIhHyIT3$9d`AIcc?1@8Z;&@Uh@wJ_z7b6KmnCQtjE%W}X4h~4EMvh7VE8asDF
zYRj8OgC0H^CdxZ(;CHa#>TpEIBn3~L=uz<wO?w>B0gqd>hrPYaPM(M;bHf&kD0>ti
zH9Z}?Mr(G%4(%iIKP{e%fgeMq4=J63A7f!`<v=BfS1^Mr2eRaeVV=G7=EykXdtGv^
z6PB?j!=1CwRuwb0GPa*nt%aRG+^IHtjwqM8a(0ZZioM8IX>zJ{ke>wkuwB(A55w=6
zY?XtMAgRC`ZmKD%a%x`{2$G4)AK}x>CWIoDd$*kZFXBEqX9sk*qb5NbRAn4f)jx4m
zt>;crt${DzC3e=Jx`F5WW#RDBw+8wrmX;q<h0^kqUBdj$KnLC`_fG`2!Lz}+E)<U1
zWoi+<?J9=6cjnbPE9#^MMGyx8iPx&u!I55CTDr{FB3%I}`7$j4^&kvDt)&G>C-Y>i
zufroF6|21*B{%nuzT-P0xQ3isiwokY^aviq7mEc$PC|xsKU1u<C+-B0*0mh_+=>^I
z4%{Sf`8e8;lW`WWW6$BUrQz`m$SEr<80`@_Kh8!_EmAxZ_ewLe2myP(F+!>laSd@!
znt_~GU_c&RJlX81JG?T!9v!;WKF8sE2|o|9Qwd5V{*W4xsUB4gniw}TYY+JtS%*YG
zlNV$7pea4|mWh_S#}n4jTKFTsv_^5lfifcdH=T99zf#ysA1R^_{GyR}AUC1|ga2~}
zVny$zcrz*0JE4U)Qma|0)f=(Z{k)pr0WmN#2Ipq3j1)AvPwCHUT)IGMm5T&Q%Se@A
zH@@1xjGOghZ&!-`JmtN!I#pwVPU$|W(QA7X_uAf{mAZ71>!d;wu@tUTm%!7NUbMd!
zN8^L=Vht>ioojJFFjuAKg!!>EBt_%71D0|oR^#2z5i1pi9=UG$Al=!Xbjd;f;+du7
zx>~MtR~+Gu>c@7B4-fAb(01ifr+*q9{z<4HmxNdLDVR#oqF%{$Fu08f5K}o6IrE*T
zL)RSAbvQ^Lm(!|n(yHi?E*35Ik0)>paX~1m?{?O-n1-?sZ`s&qNc}XOR5Kz*bKWQ#
zfL`1>s@L<2)@i+TNY3tm8*PN6{1b*y=b=%d&VO}lxbxNUDL7yJ7h~5<qJJn3(_!vS
ze>e71Ii>e=0`_xrnYNBxfvaN3r==g)Q9NHqPu!}nqemerXDAmz=bfB--m-`~KY%{v
z@?XPScvHo>2jS6z|1=0Z27Fp527LH}KnqDI$Mw9T3p(+x1WrJ?+vy*_0>4#cmleBw
z$}S(b%ilshD+})KFZjlyQE^~aY+#<TV4A^mnO#_I%XJ3EugGh$t&OFaXegol>yJE+
zBs#TQi?UfJL+5OtqxKi6)Y{}NNHi3&<BP~k5<I(P#=eVq>SBBF9XPDZQ6wq1vxY@{
ztkH0gXr77~lJRqwW@KtRj}ofj`yC}Z1m6QOIFT?_a$Tj-1U$*M(a3KsJSMxzq-Ktq
ze#L4oK*V_7-i29n;)JC+F{qGg&zgg=yDasKugRa9^|*8;k`D9p2TGa3s@qj1Ni3-~
z?H*Gfn+eZGOY`DiQ1?FnZq@T|*@4b#@|}SIehl@C=*}N+2=bb2YcUfh9x(d^GygWO
z8F)#)t3Lo+0XTmRh7xxMo=0YzqS&HpfIVO}x}gz3v=K>}5zk+L>U^(1*XwodRofc8
z1E&i1#PJG7p%fwTK3b)g!dACc-oM0Fc^~|L8^zhS%D3>R01)fq0vd|E&`=Dgq391o
zVWW6Orro6uzt4d^<b(2EXi!?5fj{7PZEJc+{I`|$X#*lL@T!4SK9^ADcGi6WGhd0!
z+{O+=rYJv;Vt70%?d@u!=Vr*r$BgnIR>*RX5R6EyWlb5&<j)gB&uK^WI#?{F(XJH+
zW@#>Uo-`Za$x2U4%SeRsVLs0kDf1bgxfJU}g8~0iya7g)JQu9x4}U_McPslp^t+wC
zO~2dNtMDs-gV-W~sXAq{D3|lDR7<9I)hq_$>aI5$xO-dw>r~_~x6<u!T)`;hU0&zy
zC>MF=yB4Ft|4|Ch!`2`r@GR|O6!2evxJQi$?U?VBAGSK>c_L~u={G?n(=54*bP>4Z
zDRtVPfoK^Zm!Z;eMchyzu03W}IE94AayflYi%|lna9nU5vUajjCew6YU%y`UrS__C
z`<qnXPm_6lw%7=ri2Zj)zPIb0$oF35?|l+_&m8gIUwXawX0P||=kG1#?}-6h3cV=T
z0!7eAZ7eG=NQqkDFSRkJIWPve^eGY7s1wBiI?(q|gZ~~;^e+O2C2rFTh%+|=d1IER
z73ZmLycO#5Pcc;NUp(9{?{vyFHg%IVKOa~WwHoz^+il)WDq4Qks*RSqK#_4_jNEFM
zXDpCA@j5sGM$j%7$9BGni_0!=cFXmHS^x#&C8YNdMZ(NfYt?kaN7`Kx-EVf_f?U(i
zIToR!dhvbexMNJ4v-5=L?TVQ@VSK<fB26ssM&*|I!=d+rXsMZR!V6Y!TRXcBcoMYZ
z#Ct0|asJtmuw@IZ<$i3zk3At~HmSkz_18G*4QU8d$y>+$9&u-+*}Qq_B`Y}|aT&1>
z9>XOP6v3ZNo4m~-Zv{HmCht+xU;Yre%l44rAE1m_G<xciZj{|~P}+$r$#PV9o(B|i
z%tWlEzYle3tfmX<3AW)ZS1*2ucU<i3ppF*-X1XEGX=$8)eW=q(qCQa6_Zv=VeaF)i
z^{rolni@=@{s*!DT%je@|GG4~djJaW(a6^Lv6px-tLf`LK-mM{MaT~Hq2ebda~o-%
z0e6agG3j>U5ul`UWi-c3>JHsvy$AycgjryGdwBtV(-X(;XV8$qgmA`fkbxAm+brg7
zWfR-5ov=7%Se!B}&cKYc(C&_iN38KGbI@Jh4#e6Mo$_8A9Ix4cBxGOK8d`O4@XJ9g
zwug=oC|nsjJs?dt<6M@JEpx>b4Arum&XGEx_sW2W*GzDVHNyqerJMs8C=IX=9Kzp{
zufWJXX2m_08@0zup9}3VgY21!mc>E0+-{do0>v4R2xm@g=d?L6{IEj?wa^NLje>jR
z-|yodY18%y`es}FkUsfKT$Z-^MX)cj^nG#d`Ibn(<N6ZG#cBUYUmkb=pf3+0VtJ4_
z2>>hMTqjAA+f>a)wDv&bw}$o%y0V<EubABTiK$C>$?jr11`^w8L;1+8e_}L`UJK#Q
z?|Uf53Q#OC??vDIFY{ik&%4!zaap@q?x7wPCmiP!=J7Rc-mP|>Oo7~G92dE8f;Asj
zJv75b+6+$&8Cd}d%H3-NaxLy1_a|Bx*ea}^I|fL`2kjaoM^_e?qMtMGj4o(}&1_qH
zE!64=%xV_>Q`*TB5#E6B;%#uZp|o#8nj_%8mUdDYKyGE%@X`z@y)LRWxs~;WQYgv8
z%;Z)vFfKE)=HmybbfjLI9a*}ImtL=z=0=vT=A}18wicEoeDh6SawWZEjqc47UOHT_
zRg5fsfS2azr4u7d$3iLP76Hsv(KQd_?~Wjd;^@)@UV61&^UTQ7;5Sryjb54`S-P2*
zj@3)&M3%nKOYM5;g2>Y6d8s2*x|APyVSq<Dz%4pJWfXv$1B}xFmPG+v#Q{Ve;I$}#
z<Zt-6EnQB_%<YUGoFBiY8gA8VSP@l269*Wt1FVb!Siu2YI>4GJfTbM3tpij?0hDrp
z+jM}&C;$fsxKRgajsnQy0QovVYZQQq0~F{0ZBYP+{!IX*bO06wQ1fqXB-{B&TB1jC
zIj^Bmuc0fdh9w+ev<_e}_6E3*1Kgwon4<tjbAX$5fV3!p!5qM<17t)2oZ3$SHXR@{
z3Sb`x7^4GZM**zm09WV$xlsVGae!<cAU_IV{(fyFiQ>mB0KXW)9rBj|#qQ)a4ApBu
zE`XQu8m`}un9tpATWbOyoIoZ<fn1=2B!)nyau6g|5Tbn_eI=JZ9GJC(VCLv;wfp~1
zQK)_FOAVG_77$O;d-DIn0j>@2^{7ri%>k|p@AW8v2ln;G*Grd00a*9-M%hbWivk$B
zPaD^nMp~iQM>n!(Z`91Jh^pah4sd07uSWrVzPC3DU%Dm=;7@ydWAUZcQ2<YJfUClL
zJqqA%4lp9T*P{UP_x48TOP5ChT(~!C3AaW8bhP$H>Py?A0Cu%%BgrCGs69NK?330g
z)ZQLd!%MAEsJ$x+U~X#^YB$962DrU73bmV~0CG8iGmH$P0EE^k)SeLq@bjK1)Sejy
zuys!qYR`@W_~)J|)Sep!Aaj5lI3kGAMl)h0f43(JwOgZVaPEmh?P3%__MRxzJ~0X)
zVNVolFNy;Aem4PFbb#V0fQ`GOQ2Wd%fOmICq4qgZ0N&kEsC_{cz=OM^P<vSvfQ<v7
z{w2pBk%NgNnYlX(wJ(dRLES|FmxmEZ6u>vTqEP$tD1a|Gz>qKkiRq1X-{1fjhY?5=
zz*D=<!rW`3K#Bk)Ib?Mxp;GTcQtGv81V3pB9X1&Zddy4NX=a!1>!F~5Y#>i$ur!{4
z`R9pjz<-p;WhW?+%#QF3D?7{+BHPas6IlyS6tO0rC}!0>F_W$4iG20}Pt0L|<%tFC
z4NAyJ=&@xyjWiwe@idZeYynRrrN(CSG!kd5n5U8cVv~6qNi8PwG*VTpkf*Cj{>Rfu
zE3qt|MzV-y@HA3DEQP1rNcPXuNY}7mTPcmC3}ZZv)C&7IPa|Q%nt2*&5cVZcBe}uW
z@HA2u?4LZ1L<C#T(?~C{mwB3GkCi-)Q~+DZ(|GW+IXsPLJDbkac!;wip2m}yxp*2+
zTxR8IvZx-((|CrmY@WvBk`3l*JQ-OUPtPRzCr{&f$GZ0LvBmUHJdLLqYvXB(&HWWm
z<JrU-c^Z!)_Bl`EiNjX%G#)POU7p5sguTYocyzF5c^Xd#R>sqK5HN|S5$Cg+JdNm`
zP339C<ZL2OBhqGeo<{u4@_8CjF}sSV5$m!{o<=0g`tvm6QfB69M4RmNZa&VKZs%!4
zhU_p;Bi>`JJdG%hZQ*IeW~`d05pl6kcp7mOTfx(akJuYLjhKfm<7q@P%*WG+PuK#U
zM%2M(^E6@!R?O3g4%lQ&d$kw}`rRnMgMK2@fb4Wfc(Y5cnC7%iq8du6(+i7G-D
zbuQ9JX=Fw1bp3h(UL$^f3ZXW$_FX-y(%RrH21o)niSpN?yp*Uu%SI`oU9Bvp^Z)H4
zV@QWX)1V98J(I@c4tsv0G@vS`Fn8evg#}CcT$NiHBdWFb{3Oqh$Lk6$o?{rapvr2r
zRT`XXwXfQ9OgTg{WvMib!xBYH>Z0eNM{(@VU4%}|kTZ|x*m+JG<jmqZ*YTVp$a$OR
zWb&M1$oT`$>9Z>|iqZDjkrFAq20JSH#YTW=F1UNJ=fZ-!Gdvg2eb{jrX7-%X5L^W#
zumMq)W~j8sCU0Svm*^MEW}EzFu#JS6uW)DOjV`%?4Q}XBH{gYsyqzt05U;c7@;nJu
zZRUIh<KO7dD7bI1;7g--wzEkD#OS(fHQh-Hs~6uy3NN;`eMk=b6?z|{YqzVn(_v!P
zgdYSWa4{W#$*nAPJDS=qO4G{H^s?kuw0X&7KW$T0UN0U+jv@{ixcyi92A)Qn2to)!
z6o(*=>kuop;dzCw2Dh?oimARu#q_eBbR9w_<4*a)y!}dFS|fd7jilllahkr$zsRE~
zx5E-Se)bZ<Fv_z7MuQ7OJ~;6HvNV8KBMvhpOL#I3lFNCr2$FB|WU<44xx3<s6zlNM
zfcKush`f7|pG(tjm4@K0Qihhn4z}R(d5+NcE=arlAK-f~|0;s_e~@kDF<qx-S)KB|
zxlZ}ve5X9m+Ut(x|K)xVe7F-M&h-pf%w^BgVz#yL;~a7x=Q-E#oNUOM&2xtEoLtCx
zkmn@xoP5YB;yK+r_?ZtmxAB}Gc#a4;cAm3~=gfqhJ9thV&zS=`BG37V=PZDnn|RLK
zJM_aJGOy*CRXcPl05Y?9re}vv4nXFGJhOPmSvaqkOp*30mn&*p7KvyK2hxQbwm)uN
z0k*6wFzk96wycUivT4t*<rf%xnCJe`?Y$?75M=O5*qosay63*|2F2UE?ahcFaf4<E
zzR@PWLH~7MXoGrE5$GSyw91tov{UnKYt!i-)}EC?WM1I*pY)BgWHW38bmw4O+n;X!
zFd4Z02<HYGV82e&AhK<12Sh+98bop{vuwugc%gxF;K{A{CESib>R%a15E%gB4lK;d
z*IE&z*ZHRJ+=LJlX-nh#Rt@GOy^dW3ak~y7Y7i@Rh>tgg=-7*NsEKw|5!CoUy?mfv
zetSguJiUCfUVcYJ`J_$Afnx3?4mO43u26ZN_CAN1Y+HM0@A7_n{dej0Pl+f$-V`do
zLodHGqI{=bK1naXE28}4CLE#kUh^;$CsQAx7YL$%FNiq+@v{!`7(oo^1+k#lig7nZ
z&$cfMw->Gn9VojgG@W6fN)Gfd9mv##<H_d~0hVdty{7$}M#AKoxUL^Pg;;wF+q9Km
zYS+^5*5LQ2&x6eY!x1*W@_g7lkp9+EG{V{9hJ(>mjTqn=epH&B{*)~JNrXrABBMX2
z;d5H}xd@+&!q3I{Tx@HI#~qD$-)w6jg#^U?ng0Rz6Tbg$3*mk`2lj3u+@Bf7{VxCC
z&cglk0QWx?iTkJV`*2e}k_4%-UNrHhYig-q=ze;`VlpN8_w@)8=YK{s--q<DrAF;O
zz3#@y`|U5Dj=bMqvY76--?&~g?&2B)bT^K63tZpKk=NoPOA;v+HQ~^V#534l*_Ry|
z7FKiGn;zB<wdePh%;fxYljk_9nZ`LP`zp=OoJL!fVN7MLk!Dbe!}23oE<ibZdss-}
zb7fw&@+tRA5%=a?O~D9OQ&kKZ676%r+@hljGyXvvl&lPwyh9m`!5etwhH%)1{;^KA
z&Q@ij+cQ`;`_28clw_M)Lm5uB)*-BlvB{12hWB8?&O>8-{bQvA&XO8(-h-+T!|Qr9
z2OGa>6M*Wq47K8p(m5a>uV`6tGc1$QPT6e;#NPCJPVJ5^v4pwg@7$)t<K_O}v$B7Y
zzE<-1fa`l$qHcqd^h8`>k*b&i9;2mmaZN>alt#Poa$QJK(Hqun=krwVOyhUnd)4&&
zU^n{5X8FfvMxK8mR~;vj6oC1hgbEC|!8ZD3n9;!g^%YU{KpLj+AI6O>-li<iCGvzU
zdq9jKv&nVI21BNrUbu|z9!JQKr4%6Wn4PMsTmzs4W+UI+>tfRoSRi21mF$BrpnpC!
zz54~OZLJgk=us|^ek*+37O)yiElHAKH&vroQk%CcY3|R*>&RqkOI2T&Tmx%Cxgs#;
zZCFf&ar5KKYWFHhfiW8(Z7Qqn7S?aFRi!yhO}7MGP0hX*&n_D`Bg<>4La!W!wuP~U
z_biUT>Vd+6^JCRoB`DX{McBfN<v($+U2%iv=qhf@DQ7R6Gm`inQSM^D$GCCNqa3_*
zFZLTwVhm9+s6}p++shBd0hd%>8<U2X1hvnhf*f7rzm05pV60Jbht!SGKNOp@l7@S@
z7>ILZ84abDT<K!94r4QTPbJN5_mU=3tbg@%`0=ksPD&;zeGQ8t8#giJ$^llbRLvE5
zuor+}G62D3YS-u5I)<Lkgf%=TlRNO}cFL^$Ad)!pF;~E9<`XEa3nt-Kfl0hI)P9`C
z8!Zd7a;2*(I;3p8RX*34)+s?OVkhrXRZFY1Nq5<ikBeG&7N*%GrjhSJ9QSHI?tghy
zANQ5oxOZKrk9+r--s7GdG48Fs$Nkm-wEGBJBKtqY(c)xUE(Pn;2S1k`|A1>89>C2@
zENeu)ovrfbK`rHnVyIpt)qCt&+5!K7TC934?^7+F^m0B|Fq;0iaiW4~verSf72{b_
z@F7NGk{qs*r9324L=BsZX|G(VW%5an@xP6e&OW#k#**IgjN%n5*YP#5!syACcZq@2
z<V$ho43sWXYf*2`dlFYpqmiqe@oM-RYYou4%W5n&+#Sh()#o0WE9v};6rdwo{ptJE
z??;4nG{%Yi69pe)ol=Uhj+uD_6pPmP9PM?se~R9v3tjvMi>o?pW|PklMtV}K)Lif(
z_So|~t>+K&>OJ>pRo>C7MVIjwjYc=_9Cc;#<xR^O*R`5HU*^$1<)v6*=rhnkd!2vj
zx%byfZGZLug72?teSbY1-d`No>Pxxx;j@<Vh+KUsUyY%ao-aDzQjQ${B~{#{{*tBP
zh5UvgY5@)ZCv|*K0ZpjqK5b9~6OAyWMj*D7ao!DBWbjP!ZorW<U?j4L8rXYO!;QZ+
zbcI#iP%VU%Xi6fN*SQAmz-LGmy^%$Zi^{*m;R(n749T50O;S}<$Vz?WB!zo02`|_B
zCk>`Wp{7fI*hgd2Vm-0kAz|1DESLE(d6G(^9;hPpZU%dN5~;#dMvb>5)8Kt7!{8r3
zSSloCV*jN6@IB8dP4ik!?*m`y9GiwDpCn)cprm;CZbIaDMcD+$36MI~W!<)gab?~2
zEH;+~A1Lb{IRAY(QoifeuH$!5xAZmvDK$Le<BezZkH1iPb{Bu+QycJ8Tf#V03pl|Q
z)qe!GmQ{~H*5fsr;EE1UO8I!A6oTIp$$pDf^yi5fy#eO)qOyF0l<o?^Qy_KXV54Cj
z_XFFBgKDDo0Msmmnl8%x9RZ(`kOY_qXhAV!$_VG8_o+04Q1LQ-B=y$|QSMT=&WhE2
zOo7#;xo6jKE!J=krZ!+|A_?t)R2hk+$~EkR+i@PbnFXIrm_(?3Oc$DA7cJEWZXh;P
z>DMrp`l+T*Sc>FOCQwAwCf}>H)VVHh=QrXYRlKZAx~J@BgER#Qkb{Gb#F)RcnOZpp
zvNma1&rz0Vm^@x5h$d<TQO;718ZEd37G_OUEcn8RO+*xpC!%O#FQVw=GyH`l?S%@}
zlcuI0u0-W6R6xg=KIJMqgK;6hr*x3g`|wy^y<JUjs^k@*M?86arnY*<Yt?>?odedT
zzp#32W~tu+?~KhV^)HHrbPTDwqjmI}{${k+cNjcZy8>~)41x)_8n|nP#ct~UwP)F)
zT%V|_Ti}DXwFv6Q*m{JW4ptCK7!AF3qYz*np1&MOP5+V?!b?fSFuqJ|r@Y+}u*I`M
z_iETIWRO&ZBtkLwQbcZAf6M;M>FqMly%bHr`j;-NGLDK(@>~Vvh*NEZJkQT=r0#<z
z&rmeq5qNnDFZUc3fubB#jfPpv04PSfQIw6M1t9JfQBBxlPcnO4<cSWFX&Ca;mVwC(
z&((0cW}-RUR;RaZsP+-*Poeh*k?j~1{|v<miWeXC|8h!SQD_u}Cec#EzpnY=i+g`Y
zZ+>S#I!^Ep27YHM@;g(J-<gX1PLpzp#_#ZWve8TraLX~s?<g1BgjJ0;9qwR)2yi<O
z0h{9)>QrkSRYnKE?Za``7@mSv8pGq1Ym^}Qk(?6YX&&_}<HOAqZ8GpQ$6RudZMlLR
zZ4?Y?cnP;lmlp3VkUI1t^y$&pXV4t`2)>tS2c~xn)SUZF^BK64vM0HyTTTD0f*`&m
zh;5=jZthE1$w;48{wH4ktX_T(mG=*oKgG*kdifYC|JAFN-&=vosI5>?@DjIOzZAgZ
zIJla=B?Om;_4wmb07yaMu<0f4WFzk6i-^&NoO;nn_M{KGEC-}%^vX(VL+U{+k=xbu
z<v#A*1gPag{Lqc+Zu}Xp3DMD-2hl@*NIG`{YO$$bp(%6+>?5NmURZY|U9qA+J^I>q
zX(Z6Ke8{9L)n-kbbSMB!)d2db=~rq1h>$sqoI2t~90WltNwbq`DVYxe2;Y5ZpbV=Z
z?-N=D6C?clM9TxvhEz&KdBBiTyT32{cY0VL&=?mM2$b3j`bvouRsvZ5qsa2dmpc4?
zm8~cf5LO#X$M|E7PT+G&D>S)45P{MVh&_`Y5)7;W#^e1hzhTds2>{n%%Pz#0U4Vsg
zrT&2mBbf!HSV2S+hsB-KqIED@v<j<ZO2_(R`#CWr1~lp##1=s%8?9P})v*_q+WaOb
zT1HnyTDBf03tA}Srmc^LOk0yn{Znu6A=}nv*ou$)Yh4O@RNb!c{Nw8DA8z!V*l?r&
zo6!?PpHgaJa2m+wp`vL)rnft0ajLi5<QX8xH7|iBeTf{`2*3W+T72rqg#Nl&;b~LI
z&1-g@KXu=9^Z<Dd`^t%VM<Tqras_Herx1FT-_TF5@1G{kt9(#%<|-X@mg5P&WPU5{
z38%b+ZT=JpY`h<|$>Y<YhH+xfDH!>orQR;v+$8vW&-~a@^xnTZxzyWDS>2RXlZhtl
zqPk5=q5&zj)JXNh>Ly`zbxw^kj#TO!=*ZHQ@7eG>iLkzc4b)p{&7Ykm3ckrIj~QXr
z9QTudV{dg26>~*qYlI?makS%P9B&d^?XVZ5Zuc1to&|h7KD<C5k2hk@^0KdwhbyNA
zBeq;{cDIwx?sjbqF2!)SH-@^cxv%^(J7`DFt+&r}O&a84jaHyRDezN31iHPQeK_&|
zq3+G&nmV??@r1C1&5H|)dsI*>#iFPMqb*kxO===2SkY<~As~uC;YPp(v4K9|HmzNs
zU0&;Z-E3cLt##L0zy(pPXszN>_v%g4x)*TE{hl*(69VFU@Av)u{`md)e3-eJea_6B
zIp@sGIWkG*8aGsDeO^u!b?g@mgfByt5H<FYN63S&a;hljQH|j4d_L9-Ib-qZvursc
zN}{#MY&j%C@L)+-wFaL`kYdH)aYZso4_E<ER-~OmqZWIE2M!RNY~U4;f#?)i=F%zP
zUqlFx(d34D)KOv37;Jn&AEM@nRBC>WwpNE@=Y02z<BCuTKwa!;vZ?bHhsf`>AAeN8
zc#A$`=*zNYa#X6o@(=K>b8oz!z8zW$1LsnxlKd=Ds50Jz*`k1<GGB66sS>Jns#LkR
zlx|Bqq11s;Dk@<>i`WMR6%>$WaZ{>7jddLou4-ILRSH^^s{G?V8LlZ#r7G55<B39t
zjZpBRrBu>4ax==P!*;<>L;0W@uWg?{PN|BUKH`=^C#oNWUZR6&R*8jDgiD)9i6h|K
z*6488@!d+-kr;e&DSbFfrkNovrZ3kCMP6w!A?7CM$F=Yp_|l~a*vpmUT^c1iQhn^$
zOT}8`Ezc4S5r1Eb@Yvn1f*xpx-8W5L&2_{LiP<8@A-E0!W>3^KMRmbG^7dDHe=yhG
z-p3L06uLp^_nm|2&gm9CySbH-55Rx#$tce?SopC+3;uIB9%m6GlUf8>d7rHQ13KO)
zSPGRgb7v#IXx%Lt2w?zS*&}(@lb;fRZNaxT`1ZoU|HN*UlSj0$00?GFnntGoDnMC}
z7zM%8hq*0pYnG~ve9B-we`z+6#n#&HJQi0>6hbw05CBsMWgA`Zz1-2nSoS}miR%v>
z?V*X*I3gCa#UqaRqrizHs6}MJ6Yi#|9)mNAmP$B-DS3mek;8-v7Iv`|Z^UDpm$(<y
zE)IEmDFNa{^12CdNDaXug~8NT9l*n1xU<0_N{w|P7N!(c*R<hiVkvTo7pgZ9gmSUY
zO+S>RWdV<qoNv-@0&$tg2*m-;hu^S`&(<;D-lg)?Qc=ffno;?fcs@S_7GGYGTxN||
z!X76E|5!-qJadF)>|1C%kn4^->B*5~Tn^%6qYX6*EYtoK{_v(hS}Blfqpu=LB>Owk
zaI}dNp^3Nk>Pl$hul{aL9II!(ZNQEwg^?P%i=f_ypM+1YIh?Dvt4@7CucI#foX#5H
zgVfJ%VeBUuyRUuS*p|BvbD*%;-)Tb1J`O0Eq3XcPAIoKV19d62K*0a;G0u}J%Z0wW
zs%9_U!dd{lXEn1+qf}bX$J#pyPrI@Uq4yo4Q==PM^E&8hJaAaCu;z8D1nlu(H(d+C
z!aYCNBEEXhL2L{xzz2{z;m&q^Y8M|#?@i6z=@3^5=Si3@QdpRPcCv)}?MTub#$ygX
z5f8~BYR@z@*C}k0vI8pa1SH2Q;nP;-RqQ11x1gnSoVpoW+DTO6Sz|+loe8ezwU14+
zzHOS;eU{P%!6bjhmGW_AE&HF#n(9_oaP9MQqAl-$To)6(r&KOu-mYvqsq0y*8*o5Z
z9=M^D(3gDRKc#fFkS_2;nvUvE0jk(QER0H6S&y^p7QszN0aX%3;;0wBUq?W}6_f<!
z6c%4mBC8e$3z5EV`akZHN7bo1ej46fI4{Kg8wYCkdDgPDi;OAVKqM~O=xz8S9_XsY
zL;*`DMAd;ri4o3g%NmC$oKoWa7=lA~SV@gI@vU(#ia&|m3E#S*EjK7B618DGA^Zb!
zSm*SI2Jy`fF*bU6GgD$B<fkjQ4Q7j2<}EE&oElgKQxZ&)x%Lv97ZRza!7`woJ#|(s
z!DG=9g0pI)oyIsRisKN)B`%iaxyuJbkE4jnt<&5;%N6#2h{4x6qJEYs?McEP>HFV4
zhQ6QmkvI+Lz=0;Z!!aeFI$F2vZDOwAk7&tyL4?zKf!m%-UrzIPnJuSai8NS#67_t2
zd5%ih`lZAY7*j0dDLG2Z5hoXDm@NlgRvAtb@|4kVp#{aTG|6+r76P77>4;%CqAv<<
zo3z>EphB#gzY8JpLbobWY%!b?Z%C<>DidCMNR=eV(#qm#q0&Ak+0Gpo5(_vTT3}fc
zBr|)lqRbv9KBp`Q)bSo=N}cGzY_75^(Iji<WMZ<w?CGnpdx#3>xozpuB13z0lO7N3
zZt$mKD{NH7Pi(~@@t{zO1OKeZd6u4DarJTZ&*SJ<DD)&2ifVoY##<B{Cxpamcoj@w
zOA$2RQ?9nh35{OFl%M^n3Zsnd^vA`+LLg2Iaj!)!n!5?dFh%qbb4MEe-Lt|qv?wC3
zI3#Q%QF!U#TQ6c9qiq|}v?8@YEa_}(V@Eg=C(Oz}h{<T)t7xjzg7AJhC00+O5G4l<
z2F*vS_b_veFp!$iR1GhHmqg5YqbVUYJ2VUa6NTh29HJH#*`GE)DTH{rFud1a7=Bt4
zJ$p5w)K*C|2x<CCA2gl*lh`;uB-UUJe4cTL)NUBDZ8Z}K{NSn-xr|ls;|8Pgi2*Qi
zAYlZ9mtOX>mh*klwzP3!Ws*@*Qmd*^prT2>8j|=y61FimdWHE3%-sibch{xV>-cKR
zMQK+%nxt2}U4^+C$3+)&PYTJNu4YfZ(|)sAexJ?P2UWfu#qDQ4Zs9eWivBErO9SAF
zMMtNFCX`QGqb<42l+>abPk#1#b_X7R=)`IQ)zU_$Y>hWw2GWF{ACr{dNzd%sGmhE(
zs`7l3hs<(;u`5{qfG(v9HuXw<N~KP*e*&|)#8=krA!DlqPlWwk*3ztGO3*AT%kS4B
zd8UJwiM340OG@f@m;44Pe7EGUl>7{D;+OhjQ=kQw=2YhOJUqoK$wDnG%`8)b7vwF?
z<Czi^Lt2`(OvykJGLb3iO+FGh&eEL3lpH6&W^&yu&B;tjAo-QTlzfd6ns84Phx_P}
zc|t*uj)#>4sBs|kBUHToH~+j&@72IGLX3)6JZy~$RI{BJnrQoj-0<t&aUSKdZbplu
z?H9a5*t9oNiSM!@|DXPPzAvkG@HNCT5u>TXnjl4x68qvy&TLLQD*#%r_km<mob96Q
zk&;?+a)Y$Ymbhk}by_8&Eu9f)tI~U@Q1&PuctUxzh`gO)D<(F>FFXgAm1!aI>`GfS
zv=PDz$R)>OSc{fJu^7Sfw@XE)ypIQo)_5a$W)WbUm{%1F{75f4YH7}3N`5C0&!V=^
zEX~<W$=}IG4wqtS&SOe^5KlFmCA$L61x)E~C}`^-Y*W0rpGXW*R_+00*9~3xLEVwN
zb`@2S+Pi8l0f604+>cj{d?XDV;Rj*m>ugKWNFd%zMvySX-W)D}dRJLn@@L^J&nf$W
z^2a-lx5_l3Q|xgp(fEpSv08t-U9IPzMNkR_O%lMEk`ErhRBha=;=IzzpSF9HKkbB|
zZ5j?J0EixJz``;REjF4`!R&+cBOuS$NT-i|C>bCur3<*RoR5+DabmN*8w?eAK_txE
zm;&FPT7F_P`5g$<OrcWW)PUVlVyS+l)Ley@uPhY{eTW4EyFY9Bt%+6NXPLPBfJakN
zNr&|x7>D|%YX=+v*Z}D%9xkn2;e`ZJUo@cw>$4U(3}Bb6<0381F-0DVePyUf)P9U0
zV{0jvfaZ$BQP(U=`?aNZrUY$;LK(ir>PUKi1zALh+^2}N-N%FFAh!5Hx_La#64JaF
zscBf$N)e6r6b)QiL+pg492V9`AvdO7qa(^c`3AnpuEeaLkdr6~3G6yYE+fEWc0JIg
zG(zI_Vz7P@xp7#m;SUoH8NgK)82EjtBm+J*z;8%T_}7c|EdOxjRiwMPK2eo1P`0@%
z6no-Dl!nVmIn#=X44gE)2hd{}|1F8Xkm(j*td1}%$ylKNU0BsUftx~L!9+p$XkrpW
zXECMELf6Rn4a3n==K0{1i-2Kj+$-mT@JG1Q^n}6Q5kfsUl0;<}U<Wj4h#oGKB5J}@
zq0sQFkR-s|iD(2h5RLxdsf6=Qco`oBRfIC(_W@C&Y9<p^$%TOH**Zt0u&zlmUW_nE
z<X$Wli)QNh+d$ED&{^N9Bs7(AExQ$$ws;urS_^TF(jqVq<fpBLJ)^GysHq&OUMqRr
z^KWUPh~`c9zAWFU0Wu}l-rZR@(O@dPLK7jZN*fFMVe`12EoIRz1va^$30oJL5e&)_
z-uZ;=(YQAeQ{A|MY0zvH-JD?C{MpAL!fu8;S84oRG?Y@w`N2Np!<3!{m}sJ>k?qj9
z%4rc5s5L864S3iw;RAXxL8TxU6Og)aH@FOwLeGr_cRn{1+)OSI+<fyYf_3fX#iKLL
zfv`W~&j4FK?rN4pLAEUVp|T+nU*SRU(U?)@F2zgaBg`GhwKfd0;WjuXii8h$h5yCa
zhGKXsY+C38PVIv}VA8JW1M2L6egRYBjXvyR4hK?HiJ^0Gva-zPI<gB1>s}GXs78QB
zI6^H4Pnu%ciaKHDUr4?p94q=eH+Qn{cOyq|?n$5e%QY&tmG~f|_`wjfilt~0*EDVm
zD=>n0;Mi+yx$wgp@FVWb)@8bSOk;bIB)EsI(%7ari|NTy?1;n}VA}F%^5El;XH$cn
zLH%0RfMQaL21<dSwh)?8#j)THfb~`UAe`$DZUomE+(@Rh@gXTeY5S9Yc-k)054r6$
zexQL?+YuV%VZ)0T*rWn<W}%aWjzP=+?(7ZS%|C{O!d>9{B|7WM@5rQ?SKHogB(@?5
zjlL{2NK-K*h~-b>RnS_t_<oM0sHnIZzVQ7A0FmlHD!!k``2wv39|>j;^qWaQHGE(|
zbt%v{l`?AS^aS|dWkAwYE=@D~B&UYYY;GjDX6`9)3%Do1P2vVX=2Wh?W^@%`fa2zC
zt`oRsE&$vD&Kuk$^FmUJ3QOrpN(m;VFr*Y(|NSJapA@CSqI!~|f=N*fDasd%;(*jU
zs;=hnMxc@9^<DU?sNKx=^Fg`Sq7F7zsmxc|m>Bz$>iy=PTahZk&X%%Wn0Km58=xNM
zol5n7QU+gb4@CRGqhXtSyP8Goa|ljY>OrpiLH`eUshWiUZ}C#QfuC=em--4K{xdIC
zhAG<Tr52GW<fT&J>%Z|*7P%7dCh)P%yW8Wd*4!a{)!YEWR}IuU`Kly;8LEayzRFmM
zdd|zI3Ao+NC7jjG^5yF_{B@Z2@wmjxm#-2!zYpw-<uI)DCSCdRwa8V43)M}oW%KP-
zTsF%>3+Ullj)aa`zQz_8g!gwsVwuu^Nt~3Qk#JJ^&m$*Q(~6V2B>%6RRHfXFldAb2
zIjL?fP4#p(l_{Am9Rub#<roG@0r_FzkOaO)!=EEO<?^kp<-XjkM8%Pd7$9wbLNi*4
zM!rs45gKYt*^NSq<qcZ?ga*Q6%a<2}rvt$4FE_6-lxtTL41BButQE9lEi)<(8p@4j
zfC0*0=+MG+_0(AdFUkM_fEnFbFIYl$Z*W=|FkMPDFkm{X*G5PUY?vI`FyO+<wHsOW
zZhAq3EshC;I*|}}h~^m@>jZz;LDr&r-mDKCbn`EYfbDo~adfCT)DSamwYdvy{rCnK
z>e@Qj7vG~=9U@Ub2>r14F!0*d2Hsdkc*|*PXkS?MZSza81D9dx>{cX~g+Q|L8rNb5
z4c|-?8mxM`HvB%>?~P>$96U}QrG;Il>|~%!RvZ(`l-36#-7Tgv)_s9Qmtw3&4NPBL
z4IT&DeF=m4HO3e>;xo8c11R4(Px8Gbzl%ThML+5%Nd7y%l)u~);KpCpp;FJ1G*5}Y
zoaZ6&m&@c5e;Mx~@s|_HM*>qaoJ7Iy%qu95nLv2Vr#vJcGmh*Cm!bU{%knjHb09*3
zSn3!TKr#OfB_$<+sq!IQ&2LJ^vJ*amUQCLx{QzI3jb>jx@2$7&Q`*A-C@IX}zJuKQ
zlp2@=v81#jA}}dAL@WmK^sY@QS!I-+UFKqEP48*SXRkt57NsM$IU-6H&~Uh>?9)R;
zr$+hLhdjZzX&`Dl#%TFN+KN3$r~TE8MBZg=_Y)8+)ZTWjrp=cCNkylI7TAUaU?J01
zk@7_%Ang6eRLUgRK{or9)_ko6sE-p{DSKQGE35PaIWh))u)nr_^0sWSlLgL4MZ>^b
zbs%ujP`JG3?+zDpf1S1D5K%P}6J++U!R}F$#N<rXHh?U93N;{-l`wN}Aofe73Q&dN
zcB#9Z&c*9IgVnOkmR7^j-@o;6_$pe4gV_R{_YG8@mWb;3t+Q_s%0*KiVk;*k-RwFi
z7Y<8^DP7@7jN~$<i#^E_fZ})ixr%q|5K|ffDQaZO_6E=HD>3dx#3F8b(Z_^wFPBS<
zdtbT4xXZ~05HC9js}9V2rbA-h=gKAKeKeFon0Fgm5uj~)%C8lqySg{5<SWQvt0V%{
z52ZQT(>ZRaE)S<5^7_BV<Fwdq4`joCPJ^5%6sYtlJ!-zL1D5nivu*1`BB{o-UrK5H
zA+07li1X1ac0<aW;k#{bLaG*_0HRzf7|^bF$px5l@)+AX<ra|-Bt-(z7hk3conga^
zhAkojGZ}e}zhGZTm)$e&<2>58^*H;uO&}c~EreZlu8)%Q4`5oup5YWL_}D$l=_7lT
zP};8DCj@GXTp=EpCzD5R@EYl2w^`**Xjds)LEF@3UgLvQn;L2)hVE>W9uPYCw}3oD
zX(RU?Euu*IAD4y~W^Beg(&S6|prvO9US{*49IpyctxV#5r$AE!jm(d<truJ~Be-=%
z-CNBJFC@(QpKWXJyOj^f$@WhfL3DvyX=&M3xeDCl<6a>5t=p27*)vI7M&Mu>Q|O|c
zL9<zXS`QZIHduxfnSD6s(YD5`Guq^db;l6AtR0yR8Ma(Esm(5&K&4`#7t7zb`=a1R
z?O@^@8h-hDDz>@aN^BF)A7;bvJLNX2cI7q-R#8)?(1)M4ABAFIzc0N`1vjut^orAn
zXf>tRn36Z|k~_Ura8n9DAd5y0hzBJ%X5^X)Ca1Si0G$uK?V7H`?%^|K?P}XP7=E$_
z5L59tpd)@LKtz#&U2hH~0u&apna)01-OR)_V>2Y7&l4>|pB?>(I6zmfZD=F+(P2_e
z%(ON59-06j5}miJ=m0c$wV*oJ!h#gW^Ey88nn5m;WJF@{6EDcfnay06fXG<aA|KMV
z$cN6ik`Gz%%*fmgO518c@z2GX^j*Z<7Wt40VIrD)L7G=dnq95Z*cA}}h@j|Je^Dem
zUJw=0P&rVNOAZw8CI?EQa-et(Zz|UD=1P<TCAN|R8C+5z93v8feNCcLptReMN`VY6
zDbSr;L<*D`RT-mQ)m8`;?@xq4^{s?J&s`>01_tue>Zt@sTPam(#q>hsE#yheJF%_y
zzUW%^V`mNauFk5d5a`e?iu6h=3rN$rHl3Mqi%d8m3CZOFU*W?y@EVb+0eKnQvWJBC
z*K^MiS&W*<V!EI#hAHi-pxE8t)=@!d+Dr`|vI7EA9HfF7hX;|=2HF0iA1d2b{HTy`
zBLZ80+n7=2&TMg^e1thbZ>jQt5ZhizF(%3!jKRar9Wa;*XTHV=V6?|%aNWlMYdprx
zbsST`1&&GLe1W#vXhRAQ>kYE4#INE9LzvQ6C<+4}n{FDK7D^bYZP}_8Vi;*)iEIwv
zjbt#vHsm4AYWKO%N0V;i8bcvW52Py(BhR6;bs*b)$i5P?@egtB{HXZ3wQNRb`xUV_
zmCfjE^>Sx}56}h&usk86=wqW+Z8p6ApWAGng}loE4~c5;;o+p(sctoBI;*bMFmd&|
z)r9y7Am>nSR19n=Pm2UGvFnGh)Nb|b*MNFR#|@%2ER+-$#$urO%Nepa9`bBD2>~{w
zVt8$_Q<`LeenaRL!u~u^aatlw8cBpngNQIGgf@;yl=hIF=8)31gMN70zCmih-%d>1
zQGwD2G|I#Fri=^$OadZ+)4)eDe#C3}-zl><ZIwL-LE{DF9G7NP?>2h@=f%w;7=#M$
zrmYeha|x~<tmCIuq9o3MlDM1K$Z>j^rb0zcq?8ecaW}!&Zs}?fLdJVk+KZeR>5aCc
zA8%fR>yMwdkw{A4y$LId3|W+jmyRv>e3;UIP-acT&)#aDtOW*MZ{TZ;DF?KrM+=?-
z6hUD9T(elqb%+08`0oh+f$;AO|6Z269_HDG^5J^1k|1wOb%;@2RdmSi5k(0eaW{~6
z$S8X(hm~&<P+VZ!a|76g_piFxg}<F}ptj%w>G(dU<?z)$M96JPH|K=Jq}z|TBUaGy
zR+M0%jCWosmGQ0*ATnN*bhyiSrTd8?_^@1hLZ#BB+_i`fbCBRc>cdMzHUth#uQFtw
z)MGQ2%4K=WQFAljq^ojx8LVLoAjop5uWsS#Bt7qWhuLKaQCcp@x#|32VM&-MuENkp
zO^8y?g+tKoQ1bX*46c|;?jZY#!Nmyx-v-nd4vbl_@YxlISRNu2U2(R-a!`bf$HRzS
zkIsV06(LML?hsvOz=-4rU(dMN{7gZ`-6-mf24q|iN*>sW=8i%?R~cw6+&&#<OEgZV
zF}QbP>pq-H=+bCR-dPO(bt*jpLK*?G+H1*U#@#w=@WLf%=e^krZ(#AB)p!G|I8~{9
zh=UmYsBOX^C%m2S=vn>-ikT?nLxi=z5eb=4{F`e8-XtSXuvk6TUI@_70_Yp~>-wsP
z0R54F6uO{4iTAwbf<9h+djRa*Dh#F2@1((}562+lG~q!T?A?U!u4;s#v}PTJ_Ixr-
zyY0P%u2PGxU3MiseazvqvhHSe4q;Dam!j8$IAl&vZbKWj!jB<%;59x737<gAq!4Az
z_)v_ncZMIHI_r291dvO;n1~-AWJys{)2uCab*DRJqB{N*f0rpG*PQqR!ig9nGSiHg
z%M4bpUkkuXX@IpRa=pQIaNYF$K|R0Q?Sh<M5m1S$x(+n5(JRb@NZOtxZLl8A5$tXy
zEbCH91Z91MYnNPW@bu5FX(x>?u<YEQjpIY)<^XVuxeMTWm{05Q{hgV-5WGZSuF$E+
z2blvbOO-P74+NtrbfP~B0U?eHL@NmXG}!)8Y+1B8UTH}Qxeid0$1<C1>{h4TJ<+Ay
z0Dys4_KrHQ>`iQG<_OwMBv1|r32!^l{LRlWwqHB&4jwk@_}f%Zddn<*{9WL9*srK1
zBzp0TqZiLK(f!R~NV|BZY0CT2%b#v)7tz8FDmss!y<56PEL<p|x6QOMDP7D1$lWex
zvrk&NLKAIjeW94Xk^6?(%)lmmUO1LXE;^B;b=ZT?Yxwd2vgzHG7e%t_o6Ew{Y$<f0
z^fW*NUp@Fz?$W71{`zE^5dhUmmqN{RsBh#B)I_JtITf|nfsJ5FMzy(zhG$a39~T_b
zZMR2Sig#o@$n}mr^s*Uk1F+Y4Gqs!=FE~${!RtK5VAEtO>_Q{-_RpyvI9oA{OB&JK
zLLSEj7K}#)CwCp@03kV?Jh4inA)?u5D_Y&NhtZo_NK@)?H;rYt(pQmTMuM+SqDh`u
zs?EcBuE1<LxebL_!pPo$9+)i$iTz+^O9KrRd*TnLS*$wJ9+HpL$ZR>4rru9fB51eb
zjd}Q$htI@5W#uyR`odFZAGnQ5HOYx-VrZm>uM&fI8zgfNUd$GVV7632)dQ-W_1NAL
z$(?x{Pkk$^+D(p^$W?o33g!TsBo`Jxdf6ea6tVui*+2)Jw`|9ulNSN?tvyel(;pUo
zLyH6uiLcjQjwOO8&jQPNIX(n~5WE7yZ1#+{;gQoL*VFkL+cq-IwC62bY2o1-{wmHx
zw(5ZrsTM)AfKV)*HD#ut*s|pv_h~y(V@>uDq4J!AzHg=z2BL%ZX*-hHSA%cd2!M=1
zJnP94IFbvCR;>ymBNYS_!kmF+uHvV3AzhyH4?6j$lA9!QX0uks`7)c0{i@C@{j1u&
z>z<?Mmxj3Aakq>eI|ZsTcd)WQTAI7(D=bwid;)OP_NK#WLoNvKiG?8hsB}Lu0r0Q<
zqnMCQ?51KCUj>}1(F&*}OJhx!moIOC9wzC^m*3Eq9O9Ja%bVFPL3STaxv5!GfqI}C
zgn>iplppMa{4|tRdcz#lv`-f6BQtx&Xd5_%*6$(z6HBHc>ME^d2cY347#!{eptD%`
zj~2?#0&x&+@fU>`;mdiW^|_xM#G>7Yq1bt#cCe}L)Q*O~VecN>xKGI`p-LH4`6s&z
z#|3y-{s!8UC^zq6f55@B*n+qSbOxZ)9UaDUH`ZLDRo~!GFmG1_-^w4c1CQ{F-3Pch
zy9a-Q-?t6Ql$B50KcI?^CpUprS8_39$xHenV^N^Y^%;wDDelR!C|lwNkCg$($#qay
zalLJ!gyjFt-kIHr(k8n<yAuUh*t!eburuYrp_bm@x>k671rxWizQ1<<+F~oalF{sk
zDCk)%*Ol3B>jA|1wIIs@PiP8+W3QO)R|4|9S$`>=3b5^VTTi<md<Stp@ZAd{gf+c@
zTUQ{p(@zNf)d}N!XAtAzC=1u&lb6|6)BCNl8eY2}rdxasc{amq>v6FXV;C^41(rXq
zq1I79)Z<(R)CWZ~rMwLFaNyAL`%o2!c|5^au_ZUS7%0P8D0HH+Z@UFzhs5BCZiSjh
z>r(uI5W1Ap_98sco%@g!bBMuxAOnzY3!x9fK<r4g?R`(_!RrP+p+hIpVm|y&#hlP#
zF?@s2{|w;*&x^rlAP6{yFNMRW5mbRVXdklby=HH=_%Jz_QJ6j9Q;C>}_ZL<pDMV@-
z?y89*ZLCl0*NoL+wVJYJN+qOhw=vPhY(-6|u1tH3DSep;YJnI26nV{(zc`Nf*YbNU
z2Sg!!Fv@)E5K7&J$<V-+(P*HYGeARLe*rkA>qrUU=~pFf11#c7p}Q{uNY~=!l|9Ts
zx|C^kkUTaFMc*Q~7YfH0EyCeJG+-mmNm|e!TCg1#^fp;6lyS=($ZH!0RGU5hZ`wWl
zZ@`#W!)zZv7S*M4PmGlT8sz%JFAuUBV!`VNzdU8c3caVy+*?zoO|o~}CPytjbi(*)
z$`&QrJrUc`h0K(EO(d~3FeR7oDPLa4lzvQ@`x-LQ22_p3e$iWwV+k_JQCBggX#Y)%
zS^;wLL;c(0AlCB0b8!T?UQ8(p?U-H0B*6H1IEMOS@NjGb_%+%((E(I>2V@En&;Sem
zHVWk{xjH9smfWVC22=8;6T)@uR<ghD@F8$xN(%5|hk7s96Q@Y>%H?&}cl;<b2WqUb
z4(zrK74tA#v~^=0u<x3Cz!;pF0YpgpNf+AV@7s=HudqGlzNBtc<2`bqR6|!hO#yHb
z%k$2rb)&KNvCw<20D8{6(urK$dEm+X%|TjAJGJ>1=@e6Pn+B5H|GXu!P|uK)2ml7Q
zn*BjRYS5?bfxNKid(~;f_XwJK<XQq#V#VVf#d)pzj}@ovo;Xg@luz;3RG5;aRZx6+
z#Us;PqXbu%(z(qMHXpE$hXfs?S5yf>mFInwnvT)2(aVpSRRqWPXn)-pluDI^eQJmz
zpwf46n<%+KW%zVK`)dh0bD|;_76P2q)E7|^?9g~^N=D3c{h%S5IYjeFF?gGXAQ|rl
zAa7H80w2L5V+IW{Qr39ixZ>0hr2__Xd@!uNJe22P&km5D-F5ctCtbOxACMnIQ9SK>
zKSm7X2w&|(+s8now|%ywv*;Q`%iJ?EEqTAg0_~}VH5@{8JxlCBSPfwZY`ly<CCJfP
zDwP^Owi&M|55{iA;&#p-Fz{7`ZbJd<2;mA|QEMi=@UqYmGA@P)X;R453&H~<h^@gX
z3xN>@fJ-ls2*$K~B4+`7MvtyQ=8_+!A{QWhavV1pTec{@?bhKdAGdd5vE=0j@)D@H
zy;P*UO^C4Yc*~YQeIIW5w%2jTX2h0POWa8dk)m{Fo2u}N77`Hnk_KXa)DVl1_-I1v
zC4Yjvzb5)#_y&j=0huFVi-y$wbSVr>BB^^N3m)wr&j>7xB-%sEMXHPuZ3{%vC5?!k
zLv4y`%g2Wp%O|N4q@E3y8Lg89J#f7c+MwZ;z;WYiM&SW2G-CRU(H`WHG>)Zk0d|1p
zt2AgPOv~3Bfb@a^BMS&6`WhZ4^ig1Qg&oe<CGFaCb=s6d-Pimri5tTQo$>h&B7}y0
zEaVZ+CaTi5jGQ?b1x-w{(%hL|%t84=P%z-Z$>heiXbu++9dk{!?R)U-yDc|Ip1?mv
z(l>JT?025b=59LipiZpOlo<SabJNMIQf9L^%WOFYLCW4tDX@H^t%k5%gUnt?ZjDD~
zvFf4{vZeXgr>Um|<pzM;Dc?IyF{O(=6u!IW_5n8}KLkR0*gG*>sv9-R-a(v_+5EP$
zH#sQV?5z{`v3#W#tKs6YW+;7+eGW886Ftz3ZP;U<0_oCNps*ovn#R8p0xQ!rYL-;V
zk}BCQ_Hg*_ni~dgNPb@k>0$4V32B{v%;pkOr`?m}R#%(5r2)H6b_V1UOYya^4R~U)
zXQk!52VU}~fOC68<+5@Q3t}#^)Wr0=^WAC?p~)UQ-h^*&AP>e|dI~zztFMF~pJFEb
zxB&cktKnt0Iz-z^nR9rr9c__+R6lljx67QvyWSJ|b~3yZN;KVjb59|VJK^y1br2HL
z+x#N4rQZG=jx2(^&K^jH_JT6ae?R16Hg^TEL&_e#xiD#fj@f&<4bU;n;f<34W&#e8
z`iMC+t+bZ&!sNx2mG98zt(fEg3epH?Li?iVg>Xj+t=S{XA4$5DDwc1k(VmBNV)^XL
zmSzXgUB{0(>_!Z$};BE7xdL;zjOlzj?cYOhXdz&%@5lLsg_41BY}8hHEkLs4HP
zdReaYoy5O-7jJCo7B(hTXoe}*%9)Z(NK0Q$FYYGf9YK657>+nc$Tr6tG5J+QP0zsR
zS`VgllsDBqaYR#*IV_spiu{Gv<4?B!A<OJK1a!h&V6eJ~wPt^QcT@dZIk(nw$zkAc
z+2=~XS0Ayn>IRk>*8r&I4^Nk>=bpmP^$w?%W`TU-d8+Na7v4tFMIF^x$H|5C-|^Tk
zuora8z&A|dOGuXt<+CDVoj>&aaeC0wsEJhSm=Zi<s4ztOvZMDiB}l_yckoP!E$iUS
zgQh}R#VNh@dAZg)w@Fi`iS*T${K?I8CV&EBpa5=4>1obb=I6?%IHeP-Axkr6iD8Q$
z%FRRF@{Gnjy&+HcmOM1WUC4@gMmvWEh=Ma3Tr~m1B-@_pW2jE5GUPE*rk#Q1UbYOr
zA(6VC`P)mL$ozYCJoON$qKi;^Gr5lkoi1idM&c!x;)fnUli;!WQM`NojGx)3*bu4o
zw0jk&Mk?<Eou%i`8u@E_#XjJJ1cUkpW9f-h?@nYu*b1#|Ob_}fg&n<z3&b}LyY=E;
zTOAc?2s&}UK`rEU(_6Lj;%Gm!x8AbX!(L?I1sfhlL(iI>Jp<A;3A;uqHYh~uqebl$
z-&fJEw3y{*xMb*8%*ltMd#7|<kK*M?eq3Ko*#KwB722dS%`jiPTCYB9_Qz<m*FJ@x
zJsc*CL>nRlGhDz&rAdBJ>ZOK<BFET$;Ijcfp)BZ=LA^*|UJgJZT3`1nV=25m?7j#K
z(tQnoNgIBpc==p8Q+mWhBL4T2hh7I86~$&ewzhpIle96~8OuG-72Tk9xt;K_M>Vrq
zF)n(itoV6_jJwQi_8-UWDv{$aVgdc=A*W;z_HvsUN);;s(JRSy#0LB9*5FV>Rb?=*
zE%!W@b*xZ0VS=HWVKJ6_3iBhOqp_s$2770KPtz$U^?X$5coj$n?PUYMD4q>Jdqkl2
zW|5wYI=T(@ry0=Bff)Q(4B`V6d<KibL8A$wIvW~|%{>5xsdw|U&+DvS%TGKMpOYFJ
z%e+&gRa`D#7OAs+jxS)2ZpX#R1*WkvUH{{RoJ%I8sbxX}AA>4EU``H2nIWs*!v&?G
ze1EqG&O95D#PZs?K#@9dV)lTE>5&!_WcGz<discS^Vdh`W&@p@D7e9rq{(?DnoP+K
zVsK~-l{%rpT9}}~ixA11*W1V2PEs}LU0Tev1}-*DQ$C<V+u*DVr)q`vq}$IJXWp{J
z$kBEK&f*|fepJHL+5R*Pm9C~5_X0qG3L)(`Q6x#A_O4Jnsi~_yz!~O&VNfpu1?Kf!
z=H<Pp60m=}^E!Znl!A~>Z5TkD21fN?oD%hoPkjl7_r8N=G-vqT_8DSu_h{t&yZ~Ph
zIOsaci6)=qh7(IJ#w{{?^5^WEP@e@v*`9oe8XfFC1m+;wV}t{)tiNn`NNSRjT10Ix
z%-&tsqE{F<N_SHs^t9`D>9ni6upLT^It|qP8?It)D3*&lje8>=J*vkxzKDC{8)OPx
zHSkS(a_l(vHUzVl%YBUtFXPEq6D&xBiX44q0Ov*Dy)*A3-_S)!I6zJmpFH5)AMJQJ
z`Kyv1k2Q^J)Toqlt|K`g`vqdUH6uG<4;Jp#x_ZlyGU(yhBwunA*@MJG!9TYotK8-~
z8X^yx=91$vmw5|ngL_3Pl^6IC-Sxu!bWv>dS{W{vd2n6m+TISNbu4Suc?f;CI7BL$
z*M1MoAMZ62(rNhW0SDNodd+}JjenJ<+)E3A|AQ^=XS%xV1nYRYmIr)s3r%So`Mbb4
z0*%A+zp(sIQKA?;X{3xAo+k9-&oVR&+@9U&|7sZc-V%qq?Pxc{z_Hm5@jn>`rq-Zo
zlf>ZdBgm0P44?_tQj5ApVXa4#1a*W<C#t5GNd~s<zC?5=?Z=}Yu@F2W%iTios5ETJ
z<17TXelH*IvJV_7*@X^TK<xt;%jgBHNCSV67<$$al{6-52UsU-SAAB?vwSi)P+3-Y
z;pG|^t)0C^I%`FB5`<?BERK=OP_^ow!8$Q4DoQ2;9+7C;v`zlbA-2_F5{n8<NPA)R
z-Gv1x%*}9+%l(6a4LZ%1EK~G0_XUzY7{gRLz6?(%#xYwwXAIbfD)R8mhD=K6M6}e2
z$VUlg%JAy=YW|3H1jg@&NkyK8)zvuzQJhh0n@Pf?w2GFr<6LPKuCznl(|WX|?dnSF
z;Y#b_o>poDyVVE2Y*(pHCT9c<g)E09T}^HBpt1?u_ighiXr2#R<xx2E;6pOoik3V?
zN<=~-PkQ@#remH-ZSzd0d7@l-Xeaq2S}WTD^4Yp$j59E*7K3eG7$%kP*#f6)51nxG
zbiwJ=LpL}nThdm$(kfkP*FAKe4N=()TF%0Ig#0O?TRK%xG1v&pbvV0nspqHAe)C}0
z@X<?fnGeYVfVnfkeeT$UT@y+JflhHlEAdGkzQ7Ynznt%ms1A2``%}21;0)Jo)$$jN
zFu_;AayG(@Rv62N8cY8)4-tm#aV@Am8y#Z172D2HnJ^c?S{`^TI|y#*ioI-wS%$AR
zRZ*heI$O><PXl`pJ<+ku=AX#zQ6c{X!UBTqo%DP$E+LG=^H9rqC2FONt%w3z&c2KP
zn%EABEp|khy)1`3?B4JhZuX|}P``N~1VnO5b|+E>_EE*^0xqyvorED+l|7W*N$z%A
z9t4>~Fs->0rtyL_W~e4ljzocx--?Yerkp`kv0v0T-A3K2{C;ef7TOiZ^7}0pJUQQ3
zX4i4c?~(Q%?8@^v!MUfb6F$bkF?dAyyUJ2s%c_s%b+;T+>eY91!^}Q>wO(DlxR>RS
zCqUV(0s=X<ajz0J&Nk{9?XOXyJx9+U)G^;4=I`kgccq@cqT3}(cI9L7mSz~Za`Oa!
zFYw|VqeICo{uS%gck_J#nLnvhI~ETT7FXlYp`sN`&w1-Chedl(Y{lMCkH!YnW!!X5
zr*M$r)|dB*#Ve%>2x}Kv)wmm<0zXgJ%J`HdYlEE-jgM8wcy$;dmk|q@{1LOS&KkJ5
zj~sRO)(VRrI`z0fdU6J@=c{NgoQgm_O?ucOi^DWytD<mvIg_Bc6>$nFwy(3;uC7d&
zr%jR0EW(WkuA-$geWF>AJYN+)-sm!6*E0qJxL{#Km4lEQ{=yLW!Y!y=spDNo{B;KY
z2wvq-Ynjb{s7Y5k9`<A{RVoMqw%6^zm}p$oqKrkOuI1`9;kB%!M(#h!9EoynNdU!^
z5FwEyefoJGHVy?$)=JkJU9$dh8slumP63mC<pZKLu7;e^7j%)C`x&7DRCa}raYzA)
zZqgn5L|wRp*&OgRxlQuXIiOHBcffDHL0EMTA0h73i*=k5keR2tf%_%A;gMr?p!z(V
zD5_n79MRh~_LpIJ&OVB2zTEVqt6%^6*6~=)mqcA9(CjGmC*R*VTHe_@^^vjsO(Wj`
z3mLF*=}~itko04VNL=$R5s9O^I2_MQ`#i`y3ck9j0kusvoz#g{2B6Rag#MU|Xi41<
zqwOG{>fQO@Mb#SlY9e#k=MiDFj+0v_oohO0<o8D%<!hQ6`DV*K-^E_V)zj^Y;_67Y
zEQsipCKQ5ReK7YVIvpS{(80Tad6<s}OZ!l2u%E4vvlU9VOvaY^>WMOHqk4a?&1DnO
zP4yX-cU$iInLCu$lFRmF+S|RT3Z>s2?rS8flB85ZN>AuY6VZrN<7mD1NWKj%`5psP
zofD{vT`fSp{tW@@y8jN;LqE8HYMtPM=>{iEJGjAACjk{O5>nHTgR9O7*k4)zn+~<u
zQva_2>jp1hT`PdKhAD;D@s_a1>HpNHkT<`-YxoO*5sH8^`B@LBZ+;*W6b-r*4ZQ?5
zx3sbd7EwJeOhX`w@i-OqtQS(~8vY<&mj6W};(=J7`2;|)TX+LGF16|$jIN5%Ii;r=
zP1S$U&Ixo20kgOtb?5s^T2jRQ;q}&WBJf7;@><Iu^N~FQ@OXv$I$5H@{10mgm-*RR
z!h&kbwZItJ%BMk4m7(0EWXaQoQn6?}e*$nb8-4=#kmwN3SFfnSCtCawzAC(0(adkc
zd+Vi*MNg0mmuz@VbPr5)0viNL_A74MJ3&_U-Q~xC>_EGx>YK~{((s$`uu2PCs*j!r
z9xunUROIk!Z7HhNR6ERmM9yKGjtBkfrXnRiB7Tni5R3c>yliUnP-R*WVVPDuL0F~_
zpvg~ImIry{o{pZ~RcF<9<&H)t$+<&pg;6$Ind>!LCtv(2J~WFCv0sLEN07w-bS7Sn
ziQi%?0zx6NFC=DO$wQ$!&2V1BA2#xP$z40B71oyXx?S-gd{;!YQKG^&y^Aj_N~4^l
zzNv|DlG7(y4bQqb$w((Bslew!$U<((BrK%Cni?8ORQd12v-29yP>GRjn(L6P5;_@4
zy=4zDk_P?~N&a^{2&b)-(^leZ<iQ4dMdjo`Dy{v448B$gY-c;2`F<dFM!<x--I!s0
z(_fSshAu?>cV_s{g%UFi{3_~{TUz5I^&x7mOR1=f8LlDB@O?};)!8yv2Qnp2R}2mz
z9pL)#(>#Hn)nZ5YxW_98;{9r!HLivI9XZEA>?F2?446Z&P#Fg!LmW08)iIi)TM2er
z<ITA)J~&q5gOx8(J{Xq|FPV?RXk>$<%Tae7gyO41a=DGCHuRjlGQmU7KOme!l0>-0
zyK$L0xZn$j8o}Llb&uG$Y)@Y;o^5h7AFr(TGnrFaZKtiSEn58IvC3)-+A6Enp6BC(
zVC8o)K!q*pYVS0PqJ1<UtJIO;pPcGy+v=*td$?!?Q+R8OOE6XPKPj%I7y2w?7d?S=
zhL?@%nyIy2aoyu1un8d(pyjT)5!oX_ht>;j3d7TJhzlYyjSkPx8=Y5(JzdQaZhlU$
z5XX@7-udJT@nL}%VXib7(GcNYN^r5_al9IQ0_~#7M4GA)l2g^Dv8|A`%Z{7TMci5g
zzm|(4(KQZZ>mDwwhw$)6!v_N|K^`s>`t<+}OqU}VDsA^2t;0WAPs1;@3BU9M4L>OH
z*_J<aZt8XEg&nk2vlW@yTBKTIHB>|HH~nxRy^ePyCJ1$u?$hysd%MvKx~R3|u7mRy
zgOLu9l!iXeo}P4*`}Zgj&&npsVQ^v&QzNN(!zv!aJb(cENf>aL-oD|+qM_;V20T7~
ztxAR$eqZg6wp#I0y>+@0xp~xw`z_W@O|1?ShHi2l(hb;zcljQ(m!h;1hi5z9(S%B0
zRFI<s4ZmBjKBwb<(K5yw2=;lJT<(F6uBI(1>S;eM*fye$4llF6@Z(13eK;M_j&Tg~
zbXE|%DgAKO!^8msNDNj$8|~8yXA>>4zd)jAesbQOLS6I_P<6{eUyMYm&`OuY^5IK8
zQ8b4M&$}uT781qG4Px+6r~+l<fOg6GFBGmPg<m-2EWF8qf^A!qr1P1`t5q&}wRE@M
z^&qC>M)C;4I?<oJ_(tjUD?~b-q?60?igbLFfxoJ+df;WSM$CX9%ay)(T04o?{3c^|
z86xS67<y6vJt~ym^*w!8@$UB$v1!-CqR~NTSCMQe@0W8O=#~AdheRSBAk4?q<XU?O
z1Xn|9dv_sEDr!<or11176razc!%IiDMFx$BN1gzgriPxCE}gcm>0<hLhQ9@C`k9*!
zdhIJnxVpjhEC=wyR1MMZi#PxVD7`PrSuveb4F0MwwQnOmo4F48>~*eZGb1Lp|7^zj
z8le^4`j~e!8n<^dIU6Vlbgt%+t3jo;1$pYR-%o%+TG;B{%*F}rzng*GQ`mFE;r?z0
z&jnc5yBQdTuENFdTmt{5??}b;U|{%7qFJ}6aDj#j+gcKS{!h1)fmUy32;2>*z@&F?
zXqJ&`dC^N+UY_Fv4vwqP$fGAPn`g<D9+m9MOUMLkm|gYkk$Wre;ay8E3493Ubl~Id
z%E9P+$T$`Ho-(s;T(Lo}EPjsnJUFg6RTjMxe#@fe_Q*EjFOzT&dkFlJD_7Wqobdq|
zFOM$9_i;Yt{<fWAn@;tt43kyLWM0?^qQpp&V*??C%^CzgHmfiA*sN~C7wcV{%iC*!
z8zwVf`M1C=Pg3p2#Bc+ZgLflx04Z_0E3qH%RRM|1<t|;;_>S$-^-Gqo1fEC<JcA|h
zNMPYYBrpXDjH@c5K^bRNpm6V7=kVE=!&J&pkDaf9sg&Gmy=Q11S?}ItE{#T;_19X~
zvo}npK(d_u=tSxuyyvQbPN19D9Q2V}u`5DY^s(C`w>C?;j@<dIB<5ezG%<2O$G|qC
z<r#G~8W61$!`|-&NH^?5;(kQjHN^cFaX%&Q=fqu0+^>lHHF4JwcRg`85O*VSHxqX&
zakmq9CvkTXx01Nk#N9*OeZ<{Q+ylg|BkoVcttakL;vOUJapE=*_Y`r@5ce!`&lC3|
zaW5113ULM6ub$|>P25+ATR_}-#NFA0^ozJ_i2EjSONhIexbulSleh-rK119{;`S$Q
z7vg#ox4ApIzZ3TiaepFiC2=<p_fz7&OWZQzE+K9vDd9!(9Z%fq9(bUGe7KQ6Qd-M}
zs}FEqta`t>KZ>gh@D^YW3YzDm&N`q94^S=8uYZWAgH=wspxYZr{$!LWq+TMr+2z*h
za$)@iWS7Xf+X%qh6^fEdZs3^yW*?onTdcABvSkXrhzc+<#(V{tPzciTRdyfY!BmIn
zlr21>6O73HTIkJ4s-#>S)oAFK6T(_2dk815Iz;J6v6$#F^ji8YYE>e73xxb+@}Q=V
zrMas)*w7Caw3`8ibss<n(XJ7426OSnS*NI@!WjiQ>GvV0Y4(wLZrB0kDhGvX62tW;
zwiP3K!$Mk*N@%RT`}HPN-99VE>lp1x6es#4;r{GJ2f5S)xktd~IIAHtK*QUp4TU#<
z7bOObDOnF8`;dyk(@B)}R+Um~sw?GGk=I`cVry!MxP2D#Ytdao&BSPM7&O74KF`g8
zp1;=%Ik{huRDPLc=k$+Qjo9`+beb(56Z#bQBxFhJg|E;_mN=TF>mylS!7SLN!3N3H
z)nTFk;}Xvxi6MoCG&t0Uh+2dmFiflXk%~aMOcOIwHAqg))iL94l#jd?0D<Kr{UJ~|
z^%bD25I*n}go#z<fk(mTtHSq<B^T2=3){cK^KmSHPdwg&vyTIg!5F^Bps0}y<MGFY
z8GjHqC`x$sE3#?g@EsNu9%T4F5f)=;bRhShkV#6TElm0i$cZN(=}-B4$fC3j&3ljZ
z5~tW0Y)7pyG9D?#n%08c`9zXLnUgj;(xXIcoLo=<f3+&=G%|1M-bdr*vgNaMDYtdJ
z*Sc6F`^VgnaT9fXmm7`aLM+@^<b_@!98>UX7|#dXYGfgzyUtQLSjKe{f|6kyr~&pX
z6p}0?NxC4l^(y2NciUG}TL@W~oo_VT+6{3#<Ar60S9%Shz#7)Wa$i1)vb}?Zr@wS<
zp`q{5r{mFWPrmTx7hZ9XDNvgGA}x1N5N12m&TDmMm&k1P=Z}nQ)W~~#a$$%g{7YaZ
zR$Wvwo2&fm)wfXb(NfVKgfe|tRQK)P>YF-7b03nH)S9cJ4Yc}Jr!tS6B9nd176P+~
zo^QZb4)j28p<*p1tLCk35ztOvO}5PVrs}(p-h5gNo*PV_IGDSN!C}GVs#7kc7b4ao
z*JB^FHptv>t(@!KxZ8sRI(e=00*nx_*7bzvD4{LT5Gmf*$Zta^yZyyVtitcxP{EQ=
zb&<@!Jt|<&+eCfdHVOvqciw0a9$ujlvF>^B?Sb(0jCM+cm`I`bV`6lI;xRF?f-R~2
zm;&K&doh+Dkjj*C{^TrJxO$=8+;ZTgd8H6GS$wo}wBPG4w9~z9sBA@etGzO5RKeq;
zX15zf7{GClj~di&6k&%09-r&x`Ie|Q<4v+2Ise$KoBvDJcORd1>3_-kLc3W>RP5uk
zlA=|Q&+7GGYW?lp<Kpq^9z`bCADtnAz8<JD=!H%)*`pN`rHHSdYp+ct>ZQllxy>Kr
zr?i*VvRoxI57MPf1J3t=j;}^O6E@GPXx9@CJ?~jbU#KYfY4|$6@Y3Vf)A!TbYaGGM
z$IiBwDaEM5XCr*@d!q?PKOpR!)}|A!`ay~+Z?`C1=~3;~kIQ*qd#DDn#y9QaAW}{L
z_NsyH(C;zfw|;FGymMuo5@L_F7c2BHckit`s)(vx84ZZGl6kv|uOg&sJ7{u6+d?B<
zQK?c?<fBpgwo#-haZ^!jQ3=WFY5#{OJ_eYD?V$h!=G^wto-tZ@KcPJ^(>H!++UbL3
z`3M;oZCM@-Trtee$X0VRY*hPmGZN<Jujkw8wq<#wjEmCo({f0;{d6gQ(qknk?Zn7O
zOXK~7x7!xv?Bvq+kOG<go2EVnV7m6QU|CPK9}z2bZ5u&Gj}UYG>2?~{vXJ7RmKIXp
z)c?AWVo$duk^TSh@40XV@s*MvBKbv<pC$RTB!8gfkCyz#A&OSzT@0hY>%&@yACS^*
zm;7~-|1ZgZQ}SPy{2a+|XjA^=K~j4pKU4CTNq(8+zc2agB!8FWAC>%zl7CC`y$4gj
zhvW~H{Be?>DESK{zew`mmHZ8oKYAGTpOpMqsr(MoZ$~8cf0z7IlD|jtH%R`6l3yYD
zOC@)IS0wB)NK-&^wUJVpJ>l3yqJ+a>=i$zLt`Ws;vG`8vrTE%`&Ge)O08E|Tx*
zgyTT!pOO4}$?qZcdzbWkz2vWv{4&X3BKet;|GeZ+ko-u=?<M&Gl0RH(-*ZwuA<}Qh
zP^n##e_8VHN%OYLlM>t{|4GT$NdAkGzfAI9mHhW5e}m*7ko+@}e@*fqNPbWR_4`Qv
zP|1&#{Hc<kCiyQ*{u;^ODEa#(|DxnKO1`($uU?WrO7h1^zCrSnB!7YA7fAjqlD|sw
zKbQOslD}W_j}L9V4nGQS{riSA|1V4aami1T*6(wYKSJ^cOMZytD<%Kl6V(4h@=r<r
ze#zf1`Cm!?YRRvV{Kb;LK=PlL{Be>$T=IuVejmy2Ao&jlx9(S+)X(#hzr9VrANRNR
zGZ|63(jb=;zu-OUul|twZU44D>p{ZHCQ1J1vUbz8_g5Xh(uh9pZJF@THG9aoiykCy
ze6A^(Gifq%O(|S<ZlP*$Sl$yDZcNU3KEsqBmo_0I)8q<urpG`?k&}^?F2%Kk(}0Yu
zv`h%lW~HifO`JJ5D^C@c3i+k*)`2bI`m79YxO?QVp~Hucg!oxfUly72@{-d{bLM3i
zkl(4!md{BcEs%<nr9n&FgVIghoZReeZqEFLrb6j=UXCdxBRO+UT6SitDR)jzGB;mF
zv&_kxpPkF4m^s<_1ii*EXF}ZM#01)Z@;iQN+{CG@F~RwJe4No3H`y&%3QtI6rzTE|
zm$gaP5-f#lv<Z_E<Ki{?soL?0aZ_isN&jfH6wk)TYuH4#b$v$d<Z12HM}lRBjCr&2
zGB{I2PIAgZ0AwBjRw{!0C8XqL<ZyYjT))Rp)yG5Mby`-VothwvH?WBY{W#a$ik~)B
zi@)_*d0cWP3}HrgmMSe7pr1Mv<Fm3=dFGV)DhlJ4iDb@Nn3bKMr6TQ=g7dIG-9!_Y
zKnG?5$rBI5!phRjSt+FKtZYt|ycjA-o|kEYJo8PNnNh=sre<c6awm|gRa~};j-<*t
zlBz*rd4p6DDpPK5cCIS2AdJqR1QRzsJ9i;WWY-!P{6yPCG@U*rJIj!rLb~`!8i;3e
z)6I)aSzKOw1wg#Ult+L|vr`y7*-m?~!;eic#cawg)Ms&~+_dBr(_nqPDK{@8kHdx6
zs@Cz@bVZVJ$x1OjuD-myyk+p8;q2%5Y+Ql>{EQ{-=E)#=GGqyH6B4JhQ?;|=3%U8(
zS&}zCJJ&QTEOlsD>MUtOU}wlYnUScAo2-j7YR87*IL9Suu~6Cb$(d#o{Um?$%o&+n
zMpm9{eia(Bv(o2G$jx5#e0D}^JeTVXhfH%OWalnQ=ICz#|D5>b+&ojFIR|HkGu@nV
zP-H5phx{HtL7GZ)CY$o%cig-MFz$0;+OZs$o51D5T#&VdC$tUMFUo-#HHUh}>{K(U
zY^pgc3$Y<xKSPHOb-~k|nMx*Yc8)1am6M%^y+g=E(Al60%k!h{oRE=4z%I2LN}Q9B
zu?YI&teb`>6z0vLvu!cW*D_%6d&rO>s+Kx4vy)R*bZAr%<APf}6v#Q@gHtXyJ3Tjf
z(d6VsPQb;%ipfq>Eix^F716Jyf82bNDks+zk>S4HrJm5~66i<zot!nOb^U2Dh1_yF
zGYt+)eS*S|%UEPmIj6#~$VinjIn|_sWjR`c(a@Yi*>DIayUh6PoI)wWZThst$1OJH
z=H~*YqlsEzBH;zjgaeoiTOMu|vZ7Qo$*<2^44_O^rKF{&CUeQEMak(IDXJ{<qIrnD
z@=PYxyi{0kbMiB?up`hUiq8_E@p-DL*^831oa@+}qY87)<Cgk!P3ceolm<nlX6LI?
zGL!T2X!@b10w?^*@98APc#=T+OPrsPr<$IzBsn(~3IH%V!5NmP0w5P=n{!nu*^6?_
zP{UBw_{@xyg{sM>0*+wBY=Ci=*`$Jz7>`IH6GKe}85|i_+GJX`bNb2>veUTyWLVYh
ztn<9Ki=50SGT6@}S^^(hH8?HX3{*fymeliH0<p)JOUuz@<NU!rLYhB=@>H$n99Niw
zNnvdyXQi8DIV4e7Dj*-S6|`*!;v{Ka;vh&x0l%2-oS&^qceNP-<81fPmi{en>0hqt
zB{OUtfF|?jXTVOEpPWa=`LRW``jg6oaxz+qB5Q@ik(PGpp)fd^fQ{OKAX{b5GmR$Y
zOv{p@BxKPaMrSH4-o>V{Je*ejRS{+t28QKDIOlF=b~;`Pm*vls$=W2BMUIG6O*N&f
zG~-kRb&giOKr+t`%NyHzJ>{8ToJ@tP#RS7@GV*dVlM7v##MwWDKQ4#J;jVHgnF>j%
zv)f&tluUzmMdTE^Mo%V-iHU(#2FXZniWlfSHRq<7T0#&HwxUVkGZ$z<RoIND!WKP+
zjnRz`Ga^<b8Iq-P9)VE%cwB0oOg1CitV+$60AX5$V=_N^F_0bRw6u(r3?K(o$tfv7
zw5oD5(&uw|emHJ8vWt>a=4WJ?FoSamlkhxgVMz7rCTVBP89#06RPE%%InxrfQ{BT2
zapPIT93wkkr=P5qwcqbF=n4=?kL!4R%ZB7a<5G^HeiVJ+{GC9;RP)RnM<-qztdZ};
z8d!fW>|9crw8+uWMZia}y|s`MNwft~fUg1fOZ#Tae3;dS5RK0?CFhx7aY41P&H%yZ
zN;^`{BR#;~H^Q~T>7PDtTAnGF&0<p*Wn?+=tIVBVBojmh%WN_746s#YlVKa2fE1>-
zzyvg;lWjq0!mb4^NOmITj5GvE9*1kJzbZW&3Y==nftf5#&{ROS80DO=qupZj$-Emn
z4=I_Jc?LO5E$iTsaJt-pj&4m(IQ!e04uF=b+S{=yO@Mz9xn%XX;1OxaO{x9px=1Da
zWS%NH3p=lJ@?9AwL|d3O^?yz0=Sl-J!9703)!uogR)xC#{lBr^Trg{A#X;NLr=x2F
z$M9C@P>N{@mk?4*`bWtSw{R!<B5O(SPVSI$-O!w?|7@-aH+kr<^pT&OMK*T|FF*HP
zLzM<pb8C{|S!X%YdIFLlEhBeP8+s4oQ(EIYK!k)$lDX)tp{nHEblkKsO#~sH(4C$O
z<hqkyf&w8CX?$2-SPEGugltyj0KX1Rf+E#!-$Nx|H8>Z!HKY@OK8*vGX%SGj6Ec(2
zCHfbLbxNMYy1*QW2k4XnNB~Wi?IOSpK!Fl0LG~ecy1c!7DXs)9c)^^tBqOH{b%0qU
zyh6iW1vv|F(#wybm1R>+$*F~I$lp0Gttqy7KsHbUqE#6HSHKr4eOj9tusFk%kBcg0
zKGH<61Pd`KnE-8;zlj5!nV$C~P@h=eIG8X_YOx*4Fu#Q+90vqF5FNR5=qj6oG-Ymf
zrV|~>va@ptWU{ie$bw2XbJ^$vMU#R7?c^o>mYO%miN&%c0%DPgOGZN62_HbxD7um8
zFW2vEX^uF5OB0Fmlc}&9rDYQt6KM;V!DY6hvk}=N{{ujx)ITDoWFY-p@DHK8UCD6P
z1Lv8US?HoG<`qf=eV&XGQqFnSY8@r#0v1~A;!f$`yOBQmPbPP`wtb54d)@ae@_GHv
zf}D|`rPw}sC-tM@Wv4rQy7f$9AKNeI%T6Bsd$?h<?+?qqEAolhdi9OoKdwp*+S2=$
zYW)J;OWSUrWp|Y<KX$5z{qnDirffQPr>690%k!%<J6En<n7ZWdyoZ0(nkwRE$-A7)
z^iD|`%iQ}bP-)$-%2K}=lwr8c&iy6#rSyLtxvUs7@tEhNhoyl>7jE+V?B3c<BQsZT
z7<asO$LZL+JHNh^*)ZHZ>zwv*;PEpv#vb|i*O4a&j+ULBQat6v{;tXOKW@6SvEP6_
z-#`D}JKGO4pZ&1)qpco;m1SOYHk=ID7i#nW?r+PopXz9spY-qE$yH+`(l)PR&n)(S
z>9l;O>gehrLBCutFa7SNzMD3Ce|GuDkE4!l75-K0Q!wJLjCpOX@4F$ZJ6zZixL`%Z
z*hR{}XQsX(pOtdu&Xk1<=Ot(MYKSzIPL|EP_2)a@nM*&D_pG|3e0lmF=C6Gx*PG&P
zTRQ!|br-*^Y}<o#y?>pjk2n>4d)d#gnheM8Cnaw%{xoINXim2ClZ%l%j<KJed+x+L
z4PzJWIr8cKJI8-sV9OZ);AGZ_31#Wu)Nh@8DlfuQ^ILC4)EtA~SGCIme;xCZSNvD3
z$Dp7f|6Q-B0&2rH&7b~#X|~GuSlYIaFDL)h;qKWNSJ$3=qW|jp^&8iosOdKL`xz?(
zHx5<L`eDmAncFK>maTto{{F}J11@e>ynpmNE@t8{zki#5^dG?kPkUOrp2_RvpK_^0
zo%;4O3l;^gow;C%|KSdo{~`LmHE5m8@4JtDmTTT(ZXEqY`M3GE<b52~-Wju}&ucoE
zZYq9hRA%?9-V3u9_dMn}AO3Us%q6FS>Yn>GJ^S3YyLYeedTso!E!}>3ZT(!~ljFyW
z-a7KnJ=G1-Q*WJ%yP3Y@Nc!}hpa1H;>Dfu6Ht0@;2R0ab`hDB@oMP16C7$f=>vPXd
zJD0xp`fFLk3U_5pc=h|_gFP&1JJ%e|4jXlG{?vE!1L_nL{da8Z>e2tHfnL*gskiSP
z?Ek}tH)n1P_gwJ(tS`h9)x!?gZ{j~X`DE{PXJ1%fo%+GRTPc5h_{jpZ-&>13zV!C}
z@Yzuv?4{{Gxt*uWyk36p$9K9c*?MtZ_;*XAdu|T+;I*S~2JZUhw=LJtEbDjfwEwZA
zKff~W;<1~}-~T#4%W|skp{~1DzA$iG<Awb7+4&Q<gj|`aEX!HIhzHc(X|w$09mS7K
zuV<{A*Zi}%Fj;#zvunXczcts62A)W?cuubVUJ;W#F#U_Bu5;^+6EpOu^Ru3LZo!dt
zXJ#HhndW~k_J+FQ>4bGVzuf!Lj<YKcZ<uHkH%0ck^}`Pp)!Pq5zV&^==bvm0^%_;b
z{Vnekhn|>zHu1;wlLLk>N&9~5bIE%=dd{Eyc6j!Xr+4{p{O4-{yL+DVnrXf65kdC!
zVLiLQJa5$S-3bxlo_jM>wRiHDzw*V*=WE_&<CGOA_v?35AE^+o-#d8n&ZU63O<x;}
z)@^z%!t&AKUaS8$rBn5DK?6=E{G4;A;7#^=^0X7{hIYBK>FE=jR`+_h#Nst$b<dYW
zzLb4%`d-BP_YaQxa`x=}E4?x^#dFWH$3E&X^U%LSOWwH@{PC-*2^+ph&i``P=NE*V
zSL<&Jp_a3|O&j)}`fA<m+4heoXa?6U%+7e2vv}RYVKLWd4pBYn>*K#Dx>u*$qb8i&
z;4^#QONlvuMw%AhX}UdR$L8u`|1!VPt9-;7pH=sFe%x)lwWLGg*)PMQuWuM57T(U=
zr4tq`@41&9GvsWNS6tx@P0>8t-y-6w_w`CVEw2cEJ9bA0t3?>_dG*QY9|QNUi8kEX
z(eL#y-u3(GU2A7wS=}t%uoGH--o2~?2^aDcXYbe^oVn(c<Ih^l{@&r;H{w^HINR~d
zx|*Z+F8uM<!FzwrIx#P|_m#qhXE!Creq0haBdJq|uH6R&GpBN*LqA{#jQwKaL(`R+
zSC)nO9-N*UT|fDgb#KToeq0gKukMSexes>^`SwESH`nT)-56o%^l|0}-&g(}s;m7y
zIHCTD2}Sqk=bJ7z7C!sb7G3BluBUJBkwZFp-rAWk>3gegYR*|x;jrsPa~-#@T>Y{7
zp<~$_^}mc=bFl8-s0zytpEuqwNZk2djOmNDlU7^QeqXNcUwdzpZ`r|hL2<LM&*_kP
zXTqUpPcC}3Lp3+P)6Cl*1K8iIax$xm+5CoyCr16<`%1)XdpF5GD=F!@vquH{(hECg
z&R;F$k3V@bb7r5oh~POzqXIsR=sD=uUb1I8$yRSm7-m_sYTi2jNW!K!yY9X7>-0O<
z_q_c@_4QxgJ=tj6>9cOFwbzz~XNP?_<ob}(KMNNg_*$3qb9T?!%|nMwxNu?A*|ar2
zdsn0+3W@zqw~fEu-r(_R^_M-<-zbS3xaQ+0wyxWI`R>POAC9iOeSGOd;qcCdUlu={
zx#7KMd_Uf>BD&<?>qC1zc{A8&<nReY`sC#g^VxVINB39#!YQLH6PDy}n4L2D#Jz*e
zl?zAfHr3s=mYg{MT&H)x^&eo}ygz5nKg!u1B@<>3Q21m<_t^7n@UYSj9R`nE9ha{7
zGO?)gUg5Nr2j?03?u}mg;++Aj|M5kK@6Ws&{8dj`;%_sD#XWd$-n<hH357p(i@TtI
zzUW^3I}s<A9_v*%uXBYpV(N}}N4z2I=v#ksjrSk7i(dSu+B7li4PE9_YZA=Ds7|B5
z^zj|AATd-KZhE%k<-#xC{Y<y>^|YREd^%`I#SiCq9=!EWYrTEm*@u+_u3tI%!-bt)
z9@Kvk_^hR3V9|y*p4}E&pVJ(CFhf1z%D5%@4`0}{&~)a`%%Z(d`zBml9Ib2mZk_Me
z8y|Q2XH?y@6}b;X-@^6(^nz-6*0B=Bj9n8v&aCzIu~#4T-muK-S=u$uYwMKG9bQ)-
zVUCynJ@CTyX#uHsd-x{2)u8l?_{eYU#9971pA76ZvG#KBKKcLZ`$X@#eZKa8s^<sE
zfA*+8_wSIuHf40*e`{nXadlJY&j)_j@eM;>P?wowf(Jx8x-5BZYggmtCEd<XFh-wv
z^7WY4-mFz`YxW;K=9=bN|B_dpnL4_1j49q@Y*M!uM`bVn@aa*zPmPp+88V{e$LYhq
z|K**?-xvNo{8xF$s8zo$4E^HuFT)(et`DkxA!4AxIB&qxL2LT=fA5Ma_;zT&!5a&P
z^gX*he8SuZPrT&!%-}n{UW(Yg`1>c{-~ZRpZ~pc4Q<c{DbDFLuy|ivdpS<^?&gAwA
zdn-RYZpz}^o836=2fvsL&XgC$ZJxBEt1@tTzj=q3eaaOVzSU=3!GSfNOCBEBw{-o`
zotb~r6fHV?FFM<{>RwjnE1MR+@JIdvkEz4vkBq*Zp<MCpyk~<KrKJ5kJb6l$ZSJuX
zpPMfIkea$((Lc?aaXwvj@Pw}0KZEs?`zA~(G{5oOgOsyz2Yq`@esjxA!)L#)Hm-hu
z&xCJpcu#Dc9ILGgeOaR!eu&LWVq%9}(U0r>DnCAc{>{X6kEm(S>@-jJJ-+4nS1X$n
zc6>E@>cuY0;(saHHARTIIP+YB>V;SSOq=oD-#$$m@rHf2;{Fq}W@@uvT=dj8bBcS8
zys+h^rt{Yhe|PcZXL*-qeLv>P!jq1x!_&9^<`uZ)a`bZpuQRI4*Pj3AUw<UunfvGE
zi%$tZm;Gt081--a53@3Uzp=IR!<wT<9(<JfcgL6B)5Ir(dfe-|q~ZR!Js;gQubb7Z
zv&n9+uvFg~IJV?Q$LSMpzWsmLd+V^MzP0gtgLKJ&q^KZBh%_P!0z>zJAfbSCcY}y@
zcZ*1ONw<^&DhLB8Axev20fK^x`dc#t#+-AW@424$dgG6G7B_o;R@^J^wPV)a11H`t
z;2!++H8pnU$xp;*V?*z0-E(bI6p?$+&o4>LbbNA|jn1i`8)NzO@|DWD7gJ<*lk1jc
z6HQa=PXp_Cp5YamB87%?M{S)~#tj+S#wdRn^>L(RJ@7yMykAL*eqcyPxA&WP>iztn
z@t)LGSa<1NwZWXec+gXi9&W-&AGs6C4#_x#Jz*U09^q-l9hOSFo9MOYkiaX=oy5So
zmYgL~oK~T2p8oO8$<)4p`IOy(j+~Jy|Lins(X2e5uXos-8Z)Tzy>2PD3*5djx|PX)
zHZqp*no5k(0(qQG^ho@>I{{HMJJQiXN_dftto;!Y=9$4=6na4`HOB)+pT7vy9!d@|
zE7lCXKu;G=?)oe&7r9uL-o{)yjAvQ?#j&u$&--n)3KiE~s!FG-Q~gzy^nsftd~bz{
zJuiC{!X=xF9&qjEZpmE8EC23Sa3{SpAL;g>@ihZMLuZLpQ{<DNX0n+lbz-%L>rD?U
z)n2xUuCYEj*?vutrj6jUR*OJZVrxUxY)9}s)4k_LSGwK{XP{m#2w7Nlv_wB7F8^@1
zkxDn+G+uCH_S}nWcBfiJC-1CtHuOayh;EqA-*dtV$C+&Dnoxc@W&2UcYi?eL(o8Q$
zmqFtzf6?lZ9LldQyOq)pyRXkxl?+(P5V?A=I}JZ^zFCz@Ll5Km)?6r=jie9fSRG-M
zj(5wiWbUtdq~J>=aaS<+>ccqtMk_HABotm}BdWnicI{pI;)Z@`c>1f0qv2r|4f7XX
zUO~i<NOB5!g;6fQS@P1mL_YOi%DPK)H*c4F<3Uck;%WYqR}i|{i=Um8ZYC3b8n{s+
z^^CrI&p{oTbC$cAHMWmFEhqQpX$}U3?`?j}aNMN$ZOWZh#nOv<M{es$ma;Y_91|Gd
z$&0id@jP<4^l{|^;mytv<NE|-&bda-bWAJyEoIx{b2O@<uX{)51N?0$_y(`rDSF+{
zKTJM2U|s9RHGpTLn%gq{DZ@!N;ph0CzlP<9qvxLMsByGXa7y7f^{>|y7DSLSG1klk
z-kh{LpXfcty?w$l`Opr#m}Np{aD*gZ;(NVhE^TAk1_8odvbj>V<833G#qZ{lenhtL
z%3IOh*2Z<_c-OAD;WEpo`iMW^%gp81a%Ne%l6GGPg2(BsMS5HhNBIn%GhD9LSTK<K
zsjYr0WA(FJM07Rn`Z?NNsUW;N91<ODC)QZt-j{FPot%$;Tw@?4)};U5X!O<P8h;Ii
zXAJLt+Ovq663d4_f4wI7Feon0`Dylh&e|`U3D<O%gDwc@T?i(QFIjOEIziMEM($92
zp|Nm5>RwOS9zGwJ5}mU8r@LF8Ck4}B*-_q|+!@EF)0tHsZF}=pD|UC|d><>agxfM1
zrS15fKaEIF%Q5`8$@&VP!A{NCfAUk@sOjssO|#c7#S!<OTWj>4mWQl6SUQkz1m+^*
zb<FrpzD%6N%eKwDSA36fst2b_#Id1Di<pQeQ{DC0Pp;u2SlIPu_l1GOPo^o8N;@l0
z%flE%Sv`vaJS_XCC~<s^e^igS-hSlv_~d4xu0k69dfEA^??Xl~If4&ro@SAo!?EnL
zFIM5Z=9)fyUxbe*!=Kl(YjsUzzFq78QXi8iM`}v3!9ZxEn%Aj$TYN@@BjoI@q%@5m
zyu7zswb}P(SF@>H3}d`#7lem-Man9aqF{0>l82ZBgWEk5=a;BI8(jUazowKSae0%4
zAjn?UMB-`raCHAj**Skv_{|AJ_WO?Yk)D1NA09t&nmDFUQk%E1eEi5(piimFq|G38
z9fengL0)g``F?+#fFEU9uWr$>k&5JsIBGJ*mC#y-^}0NlI5X>XMyYi?Wg`%eICZ6^
z_PP>z!ExT&qlyw^GCcZ@-|l~&Su0Mo(mYXCGvqr==AL5GYPm^(KZ~+|FGBvb4-_V6
zZV_v?5js3zaJJo!Uo$H@n{A$2Krs^`=<}kbaQ@la@$|9j2;wGHZvR@pd#_t~2ot)F
zB_3<0G`Fo`{<ziZq8QQ%pJjah^oGOB<+s~U8#RN+LkFnsGjz=DO1r<Ce({a6c=YZF
zjse1qz<&Dc;frRmMC6~u9GM@g+E5xjGlnncTe-N3A6gz&!F#GOMjraTh@{c(Y-8zH
z>ADR2nf6B$l^tKC1gGqm<mL=KUQCdWRE%EKXP@S;)SQJbJe<@F=t7#%vNUWr>eN4A
z9cfFw)pD=sFeh$H_BH$nqJFaJx?0jTHa;i6a6M~PSdYoEiY7~*i=Hpw2{<z@1Q}z@
z9qZ31PrKBHMiMkNp2+E1E?RAQD&$T0DE~0@ML3PPG;V<+gZ`#1c{`EAMV>@+`}7qv
z0~|X$&21)o6Vp%&{;7|qkh(oV>ieTOMc0FfwtGGuexPenGu=<syrvP|+45$uHNwyI
z<*_-M=R8U=<9=J;o~l}?)tTsBXw)1o=z!dxw(~D4&lSNhPNhy3jy`Z0pV<B;W3y%}
z>^QwwWfdX++_(iPi^t<8bm-WtauU^RFUkELY2dqvbKt_uJ4q=iACWPK=~_3QVs;9x
zZ?$|%JZ!R@V>J7Pf@}IwMh!CK<iKQUgMR%*I^G8Iz~*}fV%=@_RqtDZnKQfUn+`QC
zlDXH8Wxjc)aQ@Di*Im3Bb{e-Aw82Yeq*N*9Vj<)Xwi3R!S4x*DMo5#1f8HU1syS{D
zX4h^UPL>TPDi}MC^U>Odp!dg)sjgvwh1DQ~y@-pW9l`4#Pmg*=j&sjbKKH$1^-_80
zOY59dT<7{1y5>$9<C?f<6B7lid85hxqEo#O5OW_djka5D7I)}cNj4I^RIC$GnIh+Y
zewXB^s>mVb&u~0n<2mE?`=wTMmjxW-*5z$FeFjbHV>&E@8BaNl<zBL06d5A(N^K`q
zICK)1w)HYTJ6~U$SZCcmQcmuMD@g|Rws^ghKT8^sBLZipv!k!hRv)`<-dpp|>?6B}
zZ9x{ULo!JgF+tf|3K2<HLaR`GNS8XbOF3w<#rMedTJCDHrlX=sV{x}%J?kX*d$Hb3
zIy1)-Ha_-r^Xa0r^~?I*6VHPk13PJ-d~RjG>Qv*kK;Nw36+v`m<>z5rXG;RH$1ogH
zgLsSVpSz~jHYRpI$7$_H<Z?+Cy(h`XKB(gbYo9+<-&<nEZh|zX-BGepP?K=<nyDVO
zRi2)>@<M8k)biYv*hX1LwbpEVcArAs$W8vnpKJH-ePnvv)*Gc&pUltJP*B{46v2No
zNsz2PtINeYZPit8>C5}TM7hP#`Y7&cC+?I6(#~`D$>Q>^;Ljc9!Cgna{TX67DgYN0
zF%9D8(YkML--ma@Yg<m|N=X<3o;L7sI*Mqtw_WM$`fAkD_Vi$7BZ924+Fva@m?thg
zgK0#$mr7G>nw8Rf+j-n%9iGHnJ$d<D<H`}HjK)d);E=SSqZ#_Qp)&d!p06`yB7>PL
zUWSH0?d_SK!X@XPT%pKL4PncV$)Q;)sgq~!Eb}tiTwN$$-9o;;KO4;Uysyr}(6G8(
zRzByglid)fr}KB4^8$9&%%`uclM)i2p<)tsn3EUT>Ng~;d+)66vTbL<-+p>)Ctskq
zDUvDCBY`kim@cQ|8%0QL+o`fyAMv_%QMWChpSG)to$$V+H}qyti*NWK@YhWJWqT1k
z;WOGy{V6n2II$G%QpK#R!Cj)$y`s5&<Sgymsw8P8@zk+t!v;eTvD~D9qT^T7Xs-pW
z$8GI~+6B4xG!e0q6NzaQ>9dm=8UsURuy+ez$3A@>Om;P$G_dnoIVPpuIIE8c$)Tm7
z(mPDU%D^V-{QM#-{I<n+yAr>ZD=WH##HZAeqUJb>)dloXIg3IiLoBB|z8hDuE!=I8
zM1IJj3g!tTuCsrn$X0>WqIvVpU!H5x!poXZ(&w`Xn_}}Z;-l_(RHx6XXmxzNq!?T8
zV=?P&?7uGXdS&a!kMF%*&yb0JLxZ^&gQG;gW+oE0*LG-omzG$Z7Yoi%Ihj#LLfrWE
z5oPsY#I<!pg#5NWg~PPu6Zc+<n5`>RGH>9lxIOmHJyTxrfOL^bg*cuSf+DD~uj6P)
zhzmT5(fUgJRT;0<j4Srj(=<GY<76WK*Eo*yWJy3wFIzJps@z}!t>|jggrr8T(U;qO
zv=7#|b=P}KQa(?oJ*y_{SZijAEzJ^`wF^DHe)_SU&--y_#mZg7qfQI*r+F=BMHX)M
z5$1x+e>1f$i<7u@Td$6m^%nAnBwBIh<gzdKRkwVf&G|C1HFRNQ_51aJGP{mjbyvRE
z<q%6$goxfe!^Bo|mXK!i_-T1TLIE!~MdyVEO*`Z#U-@7WGs8Ns!LO6*V+$*XzYH}F
z%}<8J-c6%YZi;0ky_4(wFr*zGd5G0eo>A1EP=H4A0Vf4Ts1+hZ+F!fu&}E;m-KwU8
zfq|lZCo)*Kjp`{%?v&G{y?CV!9o<6+T$waAy*2EkHKnwGpwsy3uXBBpC)IR_=_yZa
z6t!?#`=t}@tBC};(?}!#t#=9*y;b&#iH;sxxqKI>L{?de3CoDsv~6LM7TjqiKFjeP
zibaKqM=kF~o#yyB*fIYW8JpMreb#h(Wu5g%$HG1G6679xRIsFEVx7M;lC9NdkY-nU
zMPBUccQ4-;7Iuw&{#Uj?Xc3>=Q55xTA+D~?qsrNck{l9BX8Vr*It{1S_S~V6r;fPt
zo%)2tr7s8ud*9;4`qkkhXhSKeHCqmkip7(shfa`PUSA+){9;Ztm8?aQ%|k+Jpm33i
zy}<d{@*{;4`CiA58z193%12ts_}=6;eZ^cagH7Wns<R&-QtxE^gw?VaQ@W`p(uiL<
za^wd;D{V`hFP-nQny8xm{Q3AZlVXpO;x3Txwo1x=5SIuGAr<ewaOonhhJ}a`o!+^u
z8%}4R-xm?4FT)Yg?F!*feO50xZudqAhVzom?p4C6viK5?b%7u3JXasFnyRUu%;mVw
zymIR#3)>dWY3~M3?zY(zTzkgyoDw81JT6SzXX-ug^L~1m$#*Wl$~iN(+ePp7j@xnl
zYS$M+x4e>1F?nhtxZUYCT|J)Nmh+E%;eAu332}p*h}Lh!l*K2&z0h0wc(N~E?&ItI
zkH6b@@YXo^%Z59New}svny_Zq7-nzl_3o;*z$ILpEzz^4cW)AzInYR&a93Cwuk~nJ
z6i+@iH@~-JdGb)3)x1rt;^ZK+5=|bRiq;8r<wOr3#H`t#Iur4r>XqgswTzcV@((gI
z6bLr%%1NnzmJMR-fj<$Hl{&2FCatt`S|&Q?nD(Z%#$_Q~D_xHc37zH%Vy)fom`fMR
z+co@-ENXW8KfAJce)TG|)||fO`!K!2@LI#SiNXe4-wqi$$z8ct#pkea_GrNTj!W}m
z?Z}5Ex1#A4@p^;TKc?+owO9!*`;z0Yvnz{!SY|%_E<ao6z45lmD&OMr+WUy{w-q8$
zZ)^<ieWz~O*c%-i_>pd({qr(G*;hu&GvB6skABI1AiZl)?Xd-|DSqq?zPS-{k$qEM
z7q(3dPx&;+U9c0I`t>v7=aXq~L*uEobGpy>A}MAhmd?+*eCn91&xwBdiDm4?Ih9wF
zc4Si%WtQtt*Qc7E@zez(O$+fxbBBe-SDbCf*cc7_jDAo)$V%bpe}3A3fL=<eS664~
zeyaGlp7EgkZrEz-pxWKi$MJnR4<B9ce?$t~9Fiq;e-h>(GtxcIIE>rMlXy2xD#2mT
zD~Vg0H+hYdA+1;<E8SeXBK72(k16v3eK{QiyV?F#BUz%<X?MQ*<YhEEvEA~*r@k%F
zuAI3wdLuUSEPss3HNrUZ1*7<pXq%{jJMW^UcV;5-l!7AqSsQ~h%_D;JD7pfU*Q^A-
zcs?4EJft0}S!@<gM}HygnJanOA~Ls>xh=ii5^uPo(D6(4TW`O+TvRGmPL))ZRsE+*
zHUsI4h2HWNdR+D_YL<lO?s7fIyCAbw;P<^ezcc+#;{!Kj0|CS9CaIFn=Ab8$bx&r<
z>JQh7)hZn}t%<g{+&+2Ix{XHhT8q|ag4V<=fsWazhI>r&!ChC3o_A&lzem}BONW(3
zIq}12s>Zt?;!V?a&&_TKp0d04;?AUKYhMHB`VAsPl+(R=^GTd=9Oa3wmXEemFX!jH
zg!IfPbu<`sb5@J^zka36IU-%T>oRw}`=Ql9NtK5yk<61}C-&5;o6bBidK$^X=5OKj
z$ZW=uRgQeOc<Gvc=1L-8g-5x9cO~fK9$qC8vudmhhmd?4qBiO8u91c6Z!BJXl^$*p
z7C!p&!hFL>JmQL%5U1puWy-KidR|LX@2ALjHM^`gxOeju({mnNImv%|F<TdLQ_1P`
zz$c>QXHq3M9QL~D&*mW2V_BQIbJFPhI8NWpZTqgkfD32#quh>9Dpg$FxqU?MB5SFn
zp1`q$rpUaV@gtriwv~@d4|m=qTp+k_9AcE~Jhs9_*SuZUqOVFbCqCNyI@HELVE+0b
zAH{tyMY}=r!})Ht)&nMZ16<QBxvH{G8K3sXe<pme)bQ7Ne(oqmD~DPWz7%I+&3Zo*
zSwunLObz3CtI3<(W8R6$jwiOo*mn*EXC_!C@<~Q;CF{L!kTupO+a(k@u2wo%ygAbL
zBWdoPJa0>+_H8<=cO1^RE*pyNk5u_)XTAjRo5{VtY$utUH6A?hrANe?&L`@y>$2gw
zL4yU2YIW_OQlD2dPE|)oxY3?lr^O4B+U=0wxWlq`g6-C2Z+P_l<Xs_yn#b?;o5U`^
z8Z}bT@UQv#j^UZ97>oV$aCzc~f@`mx<Klui-)BEf(EL&xw5)UOf}X&Il6d0a6GDzF
z<Y7HTjTed??nx~a;_rp^&?#~8-TkDlEO^p$D=Hh7mciZW&73}c%zOKhO1EM)@7Q;o
zZd<rz(N3BX6XNuFAH$rq^jEB#AJyy_@IOuZ8^1Okjk`A6^tP8c?viig+Bs`Tej3@q
z(h)Hy@P^+^C;sHb7n4leY&^ny#rL{!dZwxx97Sk|h_#Nns%I7rbNy_-9tJx+u;89V
zIsN2xWoM}<157@k$dh%d-_pa_7l+byr25BWw@0^i3pY=$)2AtXuR32Q2QwP-RQo_M
zycucsLY6(&d>6j@h0jM5o_t(bi@o;w+suis`Y-)!q;h#N3>y@td8#&q;<q(BLpVfc
z(vohS<>mdM!LHqUD|>ZzFUHV?N_c_Rt4xG<7#5{eaY%ASu01%Ad1*e;^QysTYNa*(
z@0*t;GVFs0Se{Cl$o5AMm;0Y98=1HX7j?YPZs_M3S^wbihY9^-6HW_xwIo|djxVc}
z`UFxB+Duk>QPj2e<{9Al_n*%y`w_rKbL$nj{3nfL(sT^7q%Dzn?$Ue4Y4%LYc&idI
zUf@P;%SvhixzhEcwY<k=#v~NKIqLJQ&3wMEX_Z(!R8w}soov|GvehJI7N21AV8sDy
zQ89X*zPpnXv!#hEv#E)h9eP~^R=Amyxy2v;_BQSot|s<;_7;v-?$!sPzdy#P$B~IN
zfKWn5h}<A5!V_TM83;cF5qCqBgd||!7zi`qTnPPBSwsOqqyW!@a00Ne2ZSDQ0z^$H
z2KK#yhyq4JFhUsEw+6xqxCU&20-^{YihwsjdOWbN4de(!L$n0abAx?%Ai^O0AxKXS
z_RWE?0WJkwRQwPivVd1XdXzo`AWVQWpd*A@VBZgjIN&KzA3Csa59AEs7O*7)5(7jP
z@GeMy2<%@1LIv_K0Qs|meQ?@EBnZMEfc#0pzA2ECfD6GE7f28gX~4@MJ!n9pD?k_l
zr-1xrz`ie#3xJ=3{HejdE$RU^zztxFYR@Pj%7C{)dVH|21B4cm0#%_V6axF6Ktw?J
z5Xk>9*tZ124!9C*QSBWDL>}-vke(Rq8v!{EI1A)&2KN1dNC18T@;?gp9f9xy?f_c~
zAaOv{0q^biza|hEsDJQ&|GNMY0O7s+{ci$<1#mvt;s6N*0tdXf-~akR7yu{j_rDL2
z^MJ?p``-o#7vMUuMU^)ah!WtB`~80z2n|F@go}gee-9vMLHOhS{s&{0=oH{`utl|3
zC=fZoZ}$7&5XdpWnfv{J1IR_d&-eS^0SFJ^Hn2tM1DDlAYJk7)_dhBPHK_mn{r-0Y
z0{Vftf4~1xZ3_H}i@_EZKNtw;XX2Ip{=W(Yv@>zqe*a$w0@{IiV!!|GP!C80ZUS4B
zzGxsSfIsi|zb+8EKl<Mb$T<)`yx;#;KsW$bgDon*a3BhRKkWDaH6Wnhh_m<m|0WPg
zz_a`P?*xPoa3|QJ^u+@~0RHh0_TPhL|9Q~g_P>B-|A|=kKZ<4lwb0-8zxfaL{|L+e
z%b>sQe+|q2Z(-U0G?x9hLVw%;mw&MTJ}mn$g8sJuS6KF+ie>-Lu<XAP`rH0L{e%5K
z!Lt7<=x_Ufk7fUNu<ZXOmi^y@{<i<`|6u?3vFyJ9`rH1Ou<Sn>%l^l)?7trR+y1xy
z!Ttxa?7sr~+y38T+5c@U`=7zG|90qa`~UV2_CJ7S|0U4h_Wv5o{?oDSe-g|7o1wq$
zf9D_Ue+0|^??Qju|2mfa=V00Y9G3leL4Vu-Plys92cpBr!J)>7K}Yd%C}H^c5Cc9A
z9t}PmI);xUMTvh1qQ}R<J%Y~yG2!DdQ{fXsjQBW2wD^IL6ev0+J}yWf2+}j4($mB6
z2~p_@Xz)!?>5VDz@lfdx9l;ksrRS%@Cr70xqs8yRq=#S-6>11Vgb*=A0FglW5GjNQ
zkwJ$batIfqfN-G0=<^vfCs))`aOQ(E8EXsF3D9qU^y66jz7T{uO>#gjaNUoNUP``y
zisa_wVCrP=w!g0Q@AD@&)Y{koc>Z({`a2wTLdDF(!|Y(==7w6&YvE|l<7CNWYVU+8
z$3Z+(n_ne&MX&7NKmYnm4|XQtV{KuAdM?(?)eMz|gNeJD^<U}G867<A^oPHjyA$d`
zAx!YTUg)oOJkWg5j+k`&51qLkJZE_D9PWN>OM4Tmzsh$Ij#^ObYT<@{=*hvt-P&n?
zWi0c-1KsEcwb9Gk(F^aH1<s#8%Ol7yfO?G2)s7i%ZV%K$px5!h2p|BXfe4HT5-=9X
zLI1-bYKRUx3Y~yhA#O+r5`$zQEyxUVgMy&~=svUr;qE`|PKo7U`;KbgQSZAW`;KPc
zQ9?>U59(d3xcg;5jYo2jk^y3Y1RywM0tG@n5Drct4h}BPAsjp$0vsY7G8{@AdK_jP
zejF(r<3IC1$nzjCR35az(E&#f90PESz#RpS3AkgxNdbrI9|va>cN<fC3+7*|m{IGL
zEiFL<SsXmdXyf?D6Aiys<wFqK3M_s<@aX35YGLB=+XuA>{ojQ<?)R6Yg$2qO&=0_x
z0G-Gp%=-ko_qX)cm~G8I=k9M4_P5^qTkHL;23EQN%X#??l~Gvm2PpRu`9r0I)7ssC
za<_Jg_MbeDKr+9%U_+TdMFes4^WO>3VhYcd2K)xYQXALI12v9P<`6;U5MQb~GxIwZ
zbo`h5@$)%OqInj>ZSmxCr>c;!j8Y;<<S6V=!V%1S4!SYbmrumvBrHzG;uI{-#o|0H
zMq=?O7LQ@^I2JEp@gf#4Vev8+zrx}bEMCRpH7tIE#c#3r9TtDU;&m+Ez~W6T{)oj}
zSiFtJpRo8d7Vlv3E*5{m;;&fz4U6}%_y-n4G+5)|#BekV@lnd!pU_JjvLVHQ?4=0r
zvm>x-8!$e3sZ4V}8&^&rM#63lLfwc7+4Ov!&%}4(WQq|xv_s^y@-&5N%SM6@OY&hI
zgxciqk@Fko*D@%d;;MI^d`|eWv2S5?_$vwL>L@HRyiss-P1mrOPxv(ZbtEjN4A>J-
z*Q&3@m;%Qi>>Oo%gSsE&cN>-8<poIEo-DEta3+RnQ-vr~ILBZK=K;sPm0(^{U@rSO
z46C-llsE81qnY(X5I+kOU)YFrQ|x7lbomWF^*)uF+m=ZM>M++Fsiu>kFW#f=6N-U7
zRAg%%g=IVg@teY*=g#kS(7i*#Vktp+4s$RB{Gf7x`Ha9)!RdeZ)oN18n;XgkV@O!5
zGzdRLwZLAjc9pON*e?x8Kh5C!kZkp)LN5rvfC<lSrf_*LhlC|5L(F#vD%IwZx{%u+
z?b{6{5dF#XMJFi!WXQ{(`cHm!YSUujI;CRUoiUe(GG~^AAN2A^62+=!7?$nO^VgIm
z)V+;HUP8i}w?TO(3Er_4ug9&8UX@L0cK<fao5#pKfBM*Eks<k}3!M59G-8iU%6X31
zX;LdRWPQsp+U3gdo4v2ZHI9T;xPkQdB?4*}xu?u^k+3pHls^1?DY1{=HkOW3hD3n*
zO+sfk0E=_U+aK#bAA#L+2lbsyH<eO8C%)V^0_(Z~!foU~K-FC%#dN>NlL<#V<YkM5
z6`e)tN1E2~=Xrfk0W6E*TsV$GnC2L)y$Lc-zb>!J;*gr?nKTMZVgx*Q^{E+FdUGt^
z6zM(`mWPA|DTDm4$^^rUb*x-wk+9&S7`DKQpFDHcW|d_~wX-#6X~37u_mZgS?)cs)
zthg1EAD&;wTO)xnSQ8(Hr8?784H7|p$ALcO)pJs>5^I)%M_?t?nEFHEcpfL#!T5*(
z@n<W)KyMSarKm=K@jS1_r2sJAA}c`tz3DuQ+8lN6a=*jj#=Y*N&ZDsAD_}g~oBv2C
zvVR~cJ_hStfuLRd$GYEzopa0WwUsI*)AL<vzeM3_;I2wDf$%dRe(A+LT*M|D({*tC
z0;kx!cqStn;l;^}siUwIC(H!EkLUdD%pV+J<8!<(YZO*03Ci>6j!BDfJY7)!7%Z$0
zWp8h8O*Jl`iT{Ih2Zlz1@<(9_L69*Kesa*IHC^YU_eNon45<1%6p8u#G5P4RaaaKb
zV4sT7Vp}uLi>YI<#tD?Y&?EUbPc^SAFj5w6;u^<fzpczW)^g-5j(}R)w0+kjE5=(C
z(NFwEdTuMHz|`+qx((gG+2m<>@pijxQ}wCm2(u_FAr-pYKc&WC`TnT!O60;49V^J?
zwWpG*n!qi1N2f*@D?C7(<73cHqV&C#O_5~H3-5T(73gFQkHK;%KzsLxeO_j$tgenE
zl51^l5A*F@e18@zeFBq>5}iLIWf>F79-ovgkUCQ|-5G`D4Wq`7A@wzjmY=PG!-$rw
zOuG^X>eo>my7EWiW3b>%Onuw8H251cMquqhV7%NLCauTf1}vtGUBHP}8-s-!qTAOB
zYFtnfkspVp?*M&r$a=%%%!uRZR4T1xY;%tmUajUI-uz*7mOwo@@HX4z&QJuq;(*qz
z3L&-1LIJu@$IeAS1vb+^Ujus<!_04Ni-G6$KaIoUE&zSj=boLyVs<R%z~YMfw@j3N
znM#FpNsnnC3<h&zg)bGd$WLszDMV$-zrIeVD3vF_^h|3<P^saNoO=KTTSnnR$RSpm
zESbWG6^WFWK82B3@Lp$*Q%2NeTcqC|;HzQ4|D2-1$);3(Wz;AnEZ}_3z3vva_Xcvo
zE6>i8AyyyIt(SFf1{ougLJ+ad24ZX8;Yd;4PYLqbU2WRVwo^M<q!lgidVLXzu>wla
z3MYxwUe#BxW*e3AK(9ka8!M~x_!Hr<z7bd?>iB~X=VIwHptv>)<_8?i{7{26{{{RM
zmQ`K<eZ1+W)^ae78HLq7LCps-Fy`*8_pL<0epxZcgLiQpF%=S{u$UT5|G&NadXyK;
zH!<F5ZrE-!qSbT&<D&`_ZeA?z{+1l1KZQx(;NMeh!83-kUv#*;p7wcD7bE%R;L~~G
zi1om*dn&Z#$L7^C#im`;6na~zNOBCaQ<1QOLQo$fb`i?b<P=dbzjVlh`q(diqO@$4
zI@vf1t8W18`2BTCrmemN&L}Ka7&9K0w<Q)ng5z5TEhc{<+^vAQ<}p}-7-qg(ZKrvz
zmO758**?+ON=m2lRn^qktFTrfD(-?ZLk2yE&B$?y7@ah=JNojnaQDRtG4@x8z{DQ;
zn!-EppU&KP^WX#NSg8TNQngsuspGk~wfus+Np{;$%Y~BrAmc3kO5DA3)X3EFl+vzC
z)r^rzsJvA!32LY|0OzsD^bf)gW<2V9o>dbJa>Zun?&%N&hVDjN%Bc6$DWyMh_X?Q|
zX%>aE93HPtP;M{d80_v7&6NB0L*<4rfmBgvC0UJ*OOi}xCZ}wtu2kVsJzvks*I(qN
zw3-Jc=v6z_&7Y_?PgHVGlR2gHj><*+tQie!nSI$DbMiZ^dPzA<m3AwUq~*tjp{|BA
zGpf=xTsPM;!~5aw%r{zXx6@VID2+sjWQerA3~!cj^WK*&&aWuO!B^J2uXWW_v7$%4
z?L^)>Tz-!suv?>g>}`}x*ySi<Uf~R1`$NHz-5e@X@kEq7lrO%!BQwXp$Fts*uWGOl
zRXk)fV6x*yS7jZg7JiSvS^ulhORcVozMBgy@JJ}k)T#H8TkJjeq|uT4@Oq<K$;%~r
z+pEcf#p;$bin%^>9@+TBJ7?b%1wEPWQ%K`lFywPlcT|6R9_NScN2RcYi+3Nd{A^*+
zg%$En98)Uyd#w_^RAWUhT>jwjM>EB{>%0CRSK3Ym8CIC~e7~<&_L7kNn37@o>vPhr
zmAVQtiJCPr_JX53ci9x*FTW_23tZguu^<u=o(XM|_+s%{HTS~Ox`k`b#ZZp=%{HeI
z#GT96<8IP8Cl=;07X{FLM%<&Pb(874OS>A#cD7SUQ@SnGnDFuYkvoQ-hq>3fUEm3E
zM8&Wc#AmUn7t<llyfP($S77`l`et70J&dn$4b_`vd26dRGn#7zE=_FK=_(cum%i?Q
zbAtnKJ9<mz*hlGPe5+Yf&OO%nP<x}36vhgfKi&w@HMzL)2QCU7SND_;zLIg?K<s||
zy3`fFZ@GIa;mX|~=UczVv8koc_D@TwbtP`gX)?Fcc_jxpY}l&EM(P%LN#Zh7lsvOA
zBR~8~F8nijk)18#Mw6LQLRXKvQeBosv*7p`BmVo%9fzEJh4N~yak~Bu%f;$R1BR1U
z$}NWWSqnPGgS!0pIt(74lyBha(3v55MeDq&?#ffRj>uteq6l$nL)=goMc!{qSBO6{
zn9o(8)adeTN`=8;T(L7<`Xn<Eh0L+r8T5^}uF8clRrt9tzuZ&7bAFh;%?>ZS-+21O
zHf%NQ+uJ5W8*zB%4x+bW!>_OL>k&cS^bF~ClcciQa@n63=pnXHlW$7-Q_AO08*>*J
zS6qH2Mnk8TgIDz9)*j6}0kYAknbT>~r8~>cRDzFOjm|-*RZq?;wT#M=@2p>Zfn03s
znWGez>3r#%PkPe#4rRrqZ1UYXrOFl={!iB4mtDt92}$~DR0@J@Zai_l9Wc<7G<!4H
z22s!+wj#i+%vx{rnC1h2k5c7=ozZ>QyN%ZU8g(~5EvOfZb5Xn=Ek7ZJpYXtk)k8Hz
z#eC)Ku8UntRd#47Z<S&RBeBh(PVtk3kue*gy=Fuv+1r|py$11o&$+qN56#pPY;|NS
z<=jFmEnVl_ldKeDFN;l>%!c#a8pbc+_{6Og&FJs>HDhAgW!hx6!w45qP-Y(#X=H$x
zu&8!UlFC9Z(fs^_BqRCjw^hwAww&Nc+?M<7%zDkI8}Gug8|||+@YE4LuIF6L&sjDK
zFL_)qQ_Qhb3D2kOh}Ty1)*0;QmJhUvSkkC$3E~woEZNg&MpT|D?JO6NP1bfif4wgo
zlIbL23wm44$S!iKYqoSjU$rS8E*GX?`^Kaifj^}dt`N{fjLZC>8Tu(`pn9jhMy9cW
z$MuzZc_HJOzQ-3EU#KTd+VMv9qzKbfyvxX7mQ@RrwM<W@$q$rY7X0v7piMatQS^4~
zyMxjEv(~P%epRWux@w1}yGqu6hljUtl!lbU6rVj+!CQ|v)W$9NIjbUHv1;<rJU03E
z=($4-k6T#a#pfLqcxcm4mv@Vtq0$Ri=|ZX^=W1`CzfntmeJm?kIbBG8C=!>~kEDW%
z#rEM3M1eK=nxg^^Z%%bWnOJUsdP>JFs!0tv9$(UNB^jM<<&wARzSOaS?)fuLj6B<V
zG9?+!YVP{%eRffoE-kgbRBj^Ii{WVt;aMwMHh8;yN1-K$nhZC><noImiKF3-VG0fF
z_m15ctga^AeXcmNqK;^RC~T(GmqIF1&u3?yphmQCDAuXiySO`@YV7#z^Fg)w)C=Kd
z-0$)k-T}IZ7*pAHGZ#G8fLh{4PK1HYZisrE@#e8sKZAkejI%cdqKlN0TkIB8zbeUO
zR;XVSvNl0P-Vqp)PrswYKB;trMI0Y-tNeTKBL#UKJqnw8lQl8vrf^Zmt|`IuLoX*%
ztd%|B1@8Cslr>4c3C+u{dHIVgRwmmV!w)52z1@HK$)d|E`Lu+qPjYn2>xtv1AF!Fs
zBW_6tnbQ*SltJIm)jzv)R=)V@bD{5)+`eg7`EI)Zd=9@m#pGSIRh|FpnnI$TUYmNX
z$Edq-%ahOVoVjOrZYL|>yT~T#9r!e9upxYX_+zL-n4W5Jzl3Oie7^4GV#8oW1aa@9
zC|L1VLLm|4ASVGlhm}ib>DByeuC%-H0SrIo16c12S7b>neK^IDq2QFQ(4Y{={q+WK
zJhCRTY2342HGD_Ukb-R9*_Z$^OZhckr7(pcnz@dZa4Y2;nOtMJOaa$@cmBTE)BT;-
z&&WO`QBM3i{P|PX_dLA$>51?s+_HCcjY!@ioLrBdcb}NPM5rFDZuKaPa_dfz1WUm8
z4uon1T|0*{l~oxx+t4Y$Oie_<ye}8$k51{Od{WYDQbzFN5058KF?(ytMyUn66Hlmz
zIet7eMJh^pKeWSv(9l)2#OvleZ^APKt-|XnVI>|?Z4qSLcOuK;w*vzwBUzUf%8ZRW
zdfz9LhBOZ9Hmp@E<S{>(6FNJ)xU|*(9v(sruiCO_%59!iAe{=DDu4Z6DXe*AVduz`
zA3?qAx%0*p^2zRRA~ePK0ycIA@!notQ0quY2$p*g@VVUlT07oGqk5bgy9a}%uxh%H
z{Z9SeNx7n&D@tqlGX6dH4xQX&43diB=RBVS7garJCos5kszo}ASeu-+P@K<N&wcvZ
zV^8J8C!g}TzSVd_bgQoKmW<`Q*u=%In@14v+}~j-|8QHTUGrS4(?pp}^p^^mmPj6j
z*5Q@gL0lUQqh{if59R38IvGxiy*|2Rftc;EAVhE=f=d|p3g>RTjGkF*XcIoK+Big;
zckc|GNmI=r%`;k8J)l`fjkmDT$h}bf=n-}mcndY-@MDRKQ*s7f6+;Hi3hnCQgW>I?
zrem~A@s}t_<g<<|esp{4T<}~gH_3*JMXHNKQ+1D5+2-Kwum|^xMsWUST&(b8SWJ(_
z3|P$kFHG?lvtWfoSou?6h2vs{vtuzOR(vw7aAGVzj1|AzF6SeQ#xSg$27+vGxFm6~
z;`3i{BV#%}46Awt&aY*?E|OuTC&yw^Y{t?@g3Va*4`GGlV=)z0{KHt`8d!V^i#4&^
z8@oNRm=-HPW4lk!#Pk^{8&UVKc)EO%0tkJEn@Ct~3i^I}S5ppdk+AqEEbA7y-e}Dz
zhjeOB(cmCqUF4wsTIR+WLUZ*VvYl0G2$Gy)6}o^Bw*6b*9u^~Q{^r~VM3mH?Be2X(
zi21Ig>-I&g@=9Q_6c)>3@m1{lZ#=$b1RsG#90BERwE2{)H(5p}KLShL18hy+kf7#3
zA!;=OOC!Rt@6#=Q1@lo@oH(k!q7K!cNjtnFMq$BgsQV@TPlX>c#QOZfC$ZX79xMMO
zEdBgg@ddD05Q~Md_!Jg%Vex4!2IHOS&+)E=Rle0$vbSkIW3X^u2-+ng(&xrX&yK}%
zSm_n8SP_dKU*MYw;{)x9haNwfgIGL-#ZRz!7>h@+m}uZmnx_G{J`O|OKdaH5P4G$K
z92$e=?mz+8LgQ<Du*%bo#r;_P42z#)@h}!&CKq+(jxCT0rRH|rIwHI#jMZNE^DpV~
zTa3b*3PAZhu77!6ovCqN2XG#SHxj6R+#4Bz74U=p>2b8+e<nw+;)#UiJ;pGJPX=wn
zA`(_p3+g-dqWJhoh2JOdF<2TI-~z_5?$QQEQrj_Dl@f-L^P1U*9YHt_hWS72#oRlO
zgtd%9xoj0Y?JY+H7<r44u;M;o-voE%x{W%5PdkD99$@n8p4F&0_6IZj#rWUxL&6dz
zF)TSiR=hWlgr#d>(i^R?M)e9HVUbK2{R@&Gf~R$nuvS3~FJiIthF9aF|8ISe6+~MV
z4p<7qn}hW|wh_PiBNi`V>GQuHqh&M($|H>_kIzP@Ur4!%Y=^V;GF9-K4tka0l=1pe
zSZf2S{hrli-*W9%`Z@~By^Cp|n$X0XNrQbd0cA-XzKHAEP^@<-p*3hPO3eNF`RjxB
zfAX+M-9W$xBrM|^=6>qN^nFGHx=~naCuE$S{su9J*cf*G0qm&=eSd9auhNI|9@jW5
zjvRBpO`3)QsqI6g)Im@uLfR?U#-4D;){#P0z6O$h*L`EfjpF^f6~`)(Y(uj5RtC4V
z!I=9aaol<lwJHgMX-OMH$%wD#-`(BqgNLw)c8<l%yVaa}{p8UQgIuD&prPy6hGEJ1
z&$i|h+v;iTJM-^FJdCUgw-^yD8PW-H1T_+*=fB*@h^bBwA6M^mJ+1IEIku+giyeb}
z{f2t^al<FgR@SxFBgrdP(^T+qvs-khKNwxfJ5}{CMeqYW(87gI1<9n_&hc(aOj25^
zmPC1En(e99C(YvS=!P*^<9p2g*)Y!Dthg6ruv#SM`=6xXnN0yVBrN6@=KG^^1gEv)
zANSWnyJ>7lHJ`|)+~u4rvj4)(9PK(7wlfS%ssjC8qKZ835F|tY`h#@G<-A3c``3Sr
z*W~!;BofM0@ZP3379dOZiSFs3ZR3*-co3z3pG;mXbH${nrGXlrSY+Pc@mQE&D8ba(
zY>893$cnr<fM8{E3Yqe(B=)*oeZ#O}TP114p_(da?OwlJ92YVtS!;o4>BJO|cxjnh
z0lxy<9pS5aoyj^s=g%mrHTFpb-e7#vD)_QWu}GW^Ua{0za!R0%(87}R$K&}{r95wL
zgNE3oqUYbOqh6d~R*iWJx0GotK2@?=gO`xfA)B@!omN0~Y0;rXqAB!*lU!bwB2D(!
z&}hj{x0^oS#FdH%{1?>O^x*B!vY6H~5@m}I3zQGJwusx>G76rU*iuQaKGMk*)-IBB
z!`CA-ksf|)a;A>x;gBV<;^$0rJ$BU+=ANAxBFCjjx!MBv#8Rcm0)o71?%O5NSogQX
zcCOC)DgeHNVe%XE!Pm&l;9ZQyb?*4brK%ei<?h!4`{KiVKNigO%s?px%qOV(#XeOV
zuCSj<FWxxFCWX?NYZc2dA>F?9TzHqS(nu9ElNjDKniDWIV#u{BowFycBJ?19#cg%{
zWx9>EdMEq&CZVcMs!1I77uooG(z$m|e`jfWw&RAS&j6p$O}WlaG3OMmguX9lS!j#T
z$2F2rl}dt_@4iSy!rNf!ZOMA!3MC704j0YKq$xp^3~;=k$^q10N!&<D-waXfv%J-<
zKvZ0L_4WFj)ApN_8^^5bIMt#lsTKusiym-x{LBr~wv_4QkO+JoN6_nM!rRVFJPOMs
zMIFy4dOp;Or8Xpw!ZHdl-#7BATwQt&^{C`fi*~?1J*5k6Dy$D}!dJXCZTMy^Tu_90
zng{34?@L`54!^y?2<F3jz$6Kkd#9GYz6=t|wAr@na=7c5EIW+)e5hcNioKI)Q)e=v
zE4}qy$Kg8?mKp`@=TUA#suGX!h1%QF#f}VTmk}~_iQgFCaAt?8B&=oUL>aB$%^0}&
zU^MlON&<iTt;LKBjL$PiRf|puD%Up)U;l7*$xgr6?IQA(wA$TnO_nxkgV?Kw(<1n!
zud4@8yTP>r@83@==ItH|JFaw_)&0COo@}Y5q#ea*Mz>=A$#1jni{JX%GZM?+4e=;+
zzGo=>>?Y}p8((z%%Clqen(S<gN*3d*1NV-Kd|07Y@6^*<WFrea+}JceN7l2Z68gE=
z#%3G$b6;XX>eFma)exMDYyy{Tm_zlev;|uxl?1YWMT+tWPF{beGZZ}fQo+ZMI|T_D
z@M^Z?kUe_!R;Ivt!|FP9xN2Zm&8Nngqbey%j5OI~ZsOM}d{Ycas8t&#RCa=$9_@0v
z6BRFsxvA9?`Qa5wbusRya!nhJcq(`4zZuWYZ}sO^8t{#~A0-n$W9{ZG`_s8(YFJlU
z@{CNf2LDId;2p#^d{o?4d<@o6jvD`5Gc|ahRMTdWuxLrpU$E6zy0kM5lg>tpaS~30
zR}AQJAHQ%px#xaFHom#f4!?47va=dS6i%wGn(e9`^H~YUPL6!u<Y&CGdXl=uxh7hw
zmm(jdW8N`dQ_4t5eC8)^?Zn~QIL3N0RVBGwiTkPceVr$G5jvBDzwh6tp6FHxT{r`8
z>_c$OrCoV*lHpPr6_Ko3_WhgVF|JM*scEihNZ2JQ=DX_eMr5i<xM7Vi?C&c!2J_4r
zFPO+C>YcN(u{p0XLwPp+<_&JSNDuc?J<f4$5zSp`|CubA(9LB+L+Wb}S)X)-%wC&O
ztuDN4-g42qBEr@0S=Cc(g-+Z%X{mzK#*Aa%S0rgYl(J%qbB-1`3rkzVM5rgzRc;5n
zhm;}lga#1qCXJzq3S~7MCq4OZQ<IQtsl!&hR0}E-1dt~=kJ{vUo=EvpDHZ>aSbsHK
zfij?Ns)`#z!r~8OzR!IqGe_BZY#bIXjQRc|M73kgRq~Q@G2ZpTt&B76aH<)%(Bpw}
zafs66#}3En=0OFjvA0KI(I+wU*E!D<A4C_x`M3{;=kG1=S*rx7#AUg&3tVxF@V{Gf
zLF6`>bm`L04SDy%2-m5_=<+ZHwPsJjp+Yie`Y72a4PjZ9vPn3+rj%F1s{<5XdAe6E
zDHf-SmF3>uB==nrFR6{%l#P`c+v~DpLOzyExbbCzQZZOC#U96NQ6Yutin!*ZX4Q5F
zvE(hX;beCg5;m=gW6H(jPgBId{kZrn^`n;)4}*G3wIH8_D`TU<^Y{1#8FUI!Gkve0
z-#T8KuK&H?E+;}U{7}j&Ma-<EJe>UwqAV4jlOtApX8zG-n_EXj;z=GVMMKK(>Tpkf
zJL(-CRdW7>a(R8h?JVJv=@&O^UNnj^$t85AS<3#Htmzo*c5!eWk}K}!73i5^(f4T3
zXK(q?tQb#N?;UtpaioAM2$!R2O(8<3N7~}DI%2ANz>k8fLn%_OM~dkYzkR6BWb#yf
zh-@&u)tQ&MxWf|`{a-%zGN^V51)rQe%cnU@wdL|&CIKGewWehkbe2@FM2zQy&@<`w
z>(_(@rtH0gVa{vyD<2V|)`224KL$GcYf~dmG%%?anUd>BYXm#p^?mr`%UNI9s%B<Z
z*+eNu`Bf{2tg9_*SsgDI_@uQr{R{|O<DN=NRot2w@TCl?d`Ia#PuI~cn_eS7@aSsx
z3;s1?;qzBOe}BcS_qs7U3$LFrk-l{!qMt(Lm>zODCim3W3fV@#f!h15^R32%&+qAa
zRx5<ajY%+$1XYh*Aq+ji`bH{^a?I|!-e=KW_J~p^`f#Q4**9eC@m;H$bZO>iiMnOt
ze?0RH)1okU<d>4ooJ1-{tQ4{*aoqk^{cUW5b~8pUM);6UhFAVrV8&s`Qt8u(^xdfK
zdG=`@(<#Q$S<7v;F8dQYEVpbolgK}l(_2r-*XvD~IKN!Byhb>3VaDaFa^QyKgYO@U
zTDoQh%^Jg<<rBJ%bQ_2Yv(MfTqUuk{RqS|en`QHK0?I+?i}UB?DWqpUTD^FYgdh!P
zM#Lg*RQ`6!xO4WqFYmt*th#)N-IMQ%@lq_!tEL`ninNHmmaWn}&-!HA<wnc{JM*55
zkBNwUyth1~&UYB6N5y>0&ekIet+rz-&n_Yxc^B@--q@9GdP(6`uja5U<o01>Z{@Z^
z&Z$!4Ug3hcua3nqUQtH5B;n%}RO|eqwQD6B>wm0wfUwuIsfcW~S`5R=+A!-ajL*bk
z*5gfL*3;o&#kZzAAykI{Qmw`D`I6d;TTxoq&!%ua_Erh5Uj5-tOpO@XDy<QcsZfb<
zV;W?tDvVC{GH$G78;}iJ45ni*w{MPzHHdwG$*)$RnA14X#rWe=%jD(J&mX1BJ;FKj
z>c-6bwb{j8-Ruzcl$R%{;+4i=enD}sg?d!#q+_P@dh789oSS`FDSXwU@)^3S6f(F*
zcB!V*4$&w@sDG0-6igALh@^xf!^PoM9g3eCVfA<Mquze|&?zk&pe}XCUXEr>7VG$J
zs9l!do7*6rPLx~M_R{O2(k92lp39tS$ws=%e$~@%-_{+6KFjpUr!i<gb4j~-L!4H~
zuE^6~rHe&wHtX4;yi(ioDn<_<xqQnBr%Sj)?W}{f%+@xo>djA!9U!FNhfuRGE)y@u
z6?1#4307+^e`6vRa@-x0QfM^dg^eosp2<a$d|r4pDA&lia5X2IOZXg{?25JhB}B^0
zGe5?x)?3qFo`PSPKLn3=p4Yz-AeMFCO(-I=YZTUbmIxB5WjwU-?NMg_{DgADheawJ
zi88u*leey}VTWWAJw7h?NAvo5Tt;5djnGg`@`ANoorxpPy2A7#YIRnvD9?EJG;{UN
z)?!09OPiZQY@3kLBl)+{`~w?mcjlIq6Sd0ig4TSlkcg&_`j9+S%N2SbafUBXWx-`j
z$si_6EmKUpIOwLPxM_{8{#XNzbdLKP<!zEct$|8HXI#NiSos*Zp6}tHppwR~K6{p5
zHu(KBoN~*6l|wwQDxbV!Rtfs~`Y5ck`dESw$Aqow2rM=nj6dT~T?52a`qw?{q;uM2
zKgiR39B`ntW@y|Rk&UE)ISpnHZ{kwm9J$xN0gqUEU*N)G5faK{N}ywHEE~H}%D~dV
zq+;BulYO{v1eSRQeY_neXFRjP(y1X6QR8N{m$ojxLAL4Dc+pa+k-51KF>W+*NUA5y
zPHjuQqyeA6QjFL7WN&BKNB((WPsXE6lLnl4ZC<lU;xM(mBI&cIwdR+OvD8oBe8nzX
zzRq7tP&J<Zx>16oCPGHBYkl$IF`4pLJ=VfJB_d5Sm2Bf1eAC9hM~76COf5oH!*oZz
zk}jQWs)%7pDt~uYCQvBg=9qBrhe{Kv4SiZk<uYxrD@8ZEgR3b;({H6lsAQ`-uEnHY
zdX6$W)GZ8@6{7AP;{h?@bB0VD_?#Ue)cb{9<-BcBKj4CTr7{FLKyHv3<OI1w7QpdB
z=8!%5H@~Dz4xw+w&F^ouuV4&X%fnI4$x++GLf67v+uB3k)kX`wc3s)TQQpE-&BPV%
z?5bwsqwL|R>|qc0u+p+{Mwq#)J9)}lpx@`M2;2pbs{{D&2K|0dx%s|312IEt`;PgS
zkI%k?(EfaAf4+Sm2-1MiKUW69_qV8zEU1kc5C_N<{CEB3uMX<!`EUFYAhtUQJMh&4
zTW8>d9TN_DKvqE9Ak30?Ww39*UycKIgnmC$2?ttSAsgu6h23h931Bnm;P+4DL0nVd
z<AL`7Q;!MI^V=6wpM&xrl=q;V2jw~_&p|mb`T6`R$3gxF`5xqh(qjVjd4MqU{S*h~
zLzVuwpEf8tDwZ2oJYAsA99{FjL$pEcKmF7|`5pg=p@lBzpKOAz7bf3>{0{UV=sVDJ
zkRFUH#S0D&7k>ZljXC(7VO9elX4H-k_+Z*!jnCYi4+9!txWM;^3>cmksCUr+8YfPk
ze=!)uPACN9+5I2@3~vuNP*^b1(fYukM|V6h0UJ~wsG;88eb9~;rmh|)uD==#*b5p_
zW&!d-=||~C=|t&6=>q9}P`XfhQ0Y-=AYg4CuI46aJ;3<(_fYQ}_i%OlMWDSb%pEQM
z@BkKzCSbg1PhiKGEwEmo2bCU`1{Dt#2NjM^rz&$zTS-k0wN<~YCU;3m20HLpL#WGX
z!_mJkhh`aVeGR#T{85ZhRfTKIq4(jcsu=$x6S#l<4xu|4=JyMU_WhUDF!PP~2*!W+
z+28(t6&U|jEPutv82>3O|BQZ&KN8DdyAR`!gL?=MpMa2vn1qy!oZ>JgjEedQ4J{o#
z1LIMqW5-W0vz%mQJH^hy$#t6h3=c0Kzkr~S@L7>_qUSG&iC>hEl!8mk$jZqpC@Lwd
zsH&+WG&C=1X<ydS)ziOn)xgl`nz4zgnYo3fm9>qnoxOvjle3Gfo4bdnm$#4ab-x?_
zH=%31XCTyz@~;6A19A}vs);X(VcN$PJ-*$6EjWO2&y1dlT+#D2Gdi3c!h9p84XJ`T
zK?}G);~a#d>_iF79SZ*;6g9sffK9>CvHnvW)aMc;0`Wsaz@b8TKwLN|9cG?H&6mvR
zS(F()Lt3Eca#YT$U=MYaF-QAZqIEl<<6487xq&?s^w9t_+oI-E)ZFiZo&y}gyv7Xv
zo1k046RqL5E>yS;I>ZE|vw?i~<Dq5;67=kW(QkvEWo$r76Le`Yc`}3CJka`4;i%(`
z6FOH^m@{zpzfxc{@`4a0bS*Jj9e}SJno%+BfL=`5QT0NV&J~>#YDPxIaRN1GM(eXj
z$H&yn1fBCiJMp0FhH6XHF#|Q{F+-f-SZ4;v72-m-9%@9G{VLO+?P>CFTgnk#M$|~c
z9OGQkWpVv0r7XHksH1}ox}FF9e$d~7zxvw|q;N-%5l3`uS%JDTgB-v)2KtDL(u&b;
z54M=v9`qC@#Nm$?{ZHdd;2*}<zxKPuAMGv!^l0z*kL#}xOiMeVTMu=v^MA+qz-~AG
zU-dXB(?R?FyBe7PhxQPJM1Se=K=-ieeyjdZt$}I_R5}0Dd7$sy|FS>5&^7&^?e5>@
zC-T3@4>fAd|Cjll{gt0Hy2ns@Jwbd|^jQu{$G@|r1Ka$q@j##O|DycY&{{1ZZ*<A|
z_D2?mP(63hi~m}F0qFey*xybdH;ereW9-{$KO8kHdZI_A#lMWDKlT4To=~N7MxSB2
z@6S00W9NT57W}F2+&}1Z*q;S$(3a?m)^7f5j<EZW`5fr~-;AHXb23M_zX?_?4)p)F
zQ&d|!p~nd)dcL(rADfubR{<7(jgtRtul#@X-+y0TR1C}*MfIrzn%)0xdHMh7zyDr8
z>dYT??Q)>m?cen1f&Sm~y(PMjFxG}SVmSl0L2F<}AKjeMKCWm!C=Di!`M<Q$uk}iE
znDa5BeKy`_;)96$e2}tc7Q+YYhcxyXw$B?cF?_JzCU>8y_gQM657t8=UtsuPJygIx
zGww6xJ|8{MH-llOgZ%gT_&yWu^NEA}o@1E#Airr0v+VQy<o@%q``oh62j}Y<`|P{V
z-ut|_&(8b&^PoKYtcJFISM+%a>U;y$7dq%;+`+6T191b#40`(SGfP#lhq@B|eH}{j
zN5Ao+9R!J^uOpqnI6%d8*gxOEXh7++LyrJ2@LvL*4m%gQUsle7*01rGwhO=7ixYkI
zi?TAz3K&jD4|{vCOMv>rL5+h$1P}o(C7zs|96suEmLEd@KsG0{sD>UP5p<59Usr<=
zbO1AYh!GiE8-ovMNM~>GAws<-f)FA?zkLMtvVntfXlCXHUDlT85#49hzJQ48uj`u2
z>Pj*QSvg3{#|>Nu9{e;gzmUj&I2@cw>45(l;A{?c*2D`~6;l1xj`(2clS02g#{lE~
z!D(0=3z+B+j>F=3EKb1UL@Z9i;$$pt!2O#Wu^4qv31y?*K**r~|HJ$*j`NrM*RX$e
zivRM4g1=qJzj1%Z2>iq0{Fm;}^g(|(-2c)Yq-FkpyZQe=@cGw1wf=v41mOI3|Ly0m
z5rFgC;r^HI_XzmE>i((zD2IAu#-H?O*neH+{d)h+|80GL*ZcppcP{WvmG}Suo}?|k
z(Wa$WC^SOBDlN99D!S@=g@Oe_8{{HxO`FgL(j-e#D9W^4)K1yP81BcEX+=P$Y<7r_
zIX1h=v`(fKnM203bEwQAc8(z;!vFI<=Q$^rCa9a+@3;T&m%hGzf1c-@=YBcQIZ2*`
z`lz2kR$iT}R<Bum+wFIBeeKS>)_wgO-(3H#4d32)_jmql(|7N=ck}n|yTAK^Ee~$p
zw*8?U-+%a#oj>^Dqdh;`_1NympZM{fpFH`~y+8Z;Q@y|V<<t9q^~|$<&;9!O{l9tP
z#r~HLynOJtul(-NtA~Go<PWdCKJdpk{&e)sx86SX=Xd^c{M{4p4gU50zn%R1sSi$n
zc;=(CAD=t_Pe0I#{J`o{J<$5^F8_ab`v39=ijj~1kEs84r`>0>7J7XapZt@j!oAe)
zY_Yo9i1^pW?N*D`IKk+1HCbB3*WStNYWKAkE_HcqmH@Y9vB%zQ(cH$6<HmsF4%afv
zQml*HZ62Si5Z+46Zcicqj;}=>pKjjaNVuIh@%}c;3vaSn92T+RO{>e<f^0a|-|=pB
zIpkw4{43+eCXd}`k$(@`=4_Gun{Tqo&-m3y)iW$@HaTnUUN+PN^5$J`*WRG;F1K0j
z-qym!cBdSV*KOgSPcB?$bJ}idw>i`dtN!ht_Lg?b3eByQ)2NkG^S620gWML6MGhDe
zA7E*-_}xxqRz5T2yf&BB=5YqNZ5=q%sku>K)^?Bfv(}nFV#4~^GNc?fhtX?sEVEd}
zhC`ZvbGz)Py3tweoF^x5kJaAfur~+C)9gYQGpem0=<jf~HQ6np`Dt#If8JGdH@O`4
zWjIZB^UuIKeg2NZzdDQ>w|Q-aO&-flc8A>}zaipud6wH+;3#yifc7pG=llJeTuV`I
z3iTIm^bU@yQ0)tLk6e26CJ^oxo2M<{u<CZSci7sRT<spUU#kB0=2oB0iF*;dGtl4C
z?!`z$s*61~XLGARKH(k`pYTV8TdV=$nrsdw^CE|Z+@Fa8-4yIz8tm4_`-<T4t{x8^
z$Ev^crr`6IpttfVs@pDpe_M5<_u8A9Y+610{hdK>6iD96Om$mC0W`NVZq;bW+U^Ui
z26>!wr|@}DPx7W%N>i);yy=?-okx8K*ze!wcGwr&)n5o!{dv9#m~K>mCajgT1`#hc
zCvpQ@9NG~z-vM1I$ln;jpF&<c{;E7jsF^iacCQa{cZ9fGHFt&VZWQiWb(ON)q#ak@
zIII)yg|)S^yH;~gm)+&UT~*s4yYoZNH-sENJs2E*U{F17mfih>!TH%U7@YqdgTeXV
zq`8%Q_O8L;{JS-`5?`a{R?;_V?iosXG`I2rihRwj<Udw(EBQVBp6Z?^yN|sWoZmws
z?mpq3S})3{xt08OYi^}H>ovEMey4C(*C^$0)!a&Wljc^!muqe%e9?Qs<u?lVbhC2Y
zAl$QN*DK{a5$qn&+_RMO2)D_sl&43yYZ{dJx;3|Qe7)vY^4FocXDjWaxs~#iYwl_#
z|C)QM?9S5M;*pQ$7VYt_>Q?&WA<eCPkf2X<E9L3Y+)Dj)Yi^~!yEM1bKRPtGQa_EF
zTWN2T=2qT!pQ5?1m)-fo-Eh59o}71s+bc^wU-Yju&8@_52yw>>_gs-*67Ff0qCJnR
z<4q~=pz2<z)Zek=!RH?earYlr$EVUBy~164osz#E_4q=ieRqbqyF=1nAL8y5Zd1Kd
z{~gEG`ngVaw+i>d3Z=g03-@)Rf7XT^pAvH1DBNcA?4bHp%V!8FFC7oA&$N*GO-vk_
zILvTGRAOvlls+oaV2B+ZJG;t66;<-nO`4^xGiC*?Kc?B#&vea`ckFDy0-U#sg#TQx
z%`>}ciLKc;-MN^5hMo`1^M|XmczqSkKKnA8$>nZ$YyOI#5*^J%%$62grK>{;IhPu!
zhU$kr<BaL-yuEW(Ckn2=z4P|fooUh0H8nN2-F90v##&LQsk4h>DgWXrrg@z-j33gP
zOkG@!`JG*z2t|#pjjgS%v9ZHC-0tOW_mJnaRWzGs!RC^g^0?k-ny9{^x}oO5!)srw
zUpMpXHFKZTzp`M9+y|~+#AWv`T7so*vulyt<KkNIE>aehi<;dle7OEDa@d;|@qVxE
zKD)!asM*zq6_j<+ax7f1uqwQ2QV~`K)+<UUxvfp~NiQiameQ;+^Ds&)jiIy>gImgq
zr!1?1vdqbpRhmRu8`Anyd*PoNL#gHwlv<ifsqZJavx^caxAYpyl}gDlm^6^sAKx46
z9@ZFxrtkX}`ZfwBCC5;*IhK-3hf#7#f?JBwQA~-hNQ#f5c%&C!6603#5RW{>ArH{?
z26uLDGz~8;pyAS`6ysLn<Bpd-A)3-l7gD-3pVV}EbVz5IM8j6&_{3o}(zlRCmM)-?
z()E-e>fJ(vU`JB9}2Jx7imbjL#mICXJxsg9!t1{lj{r-BFGDBAp1!?JVUaQjRs1
za?B}|Q)-}`4OxR31EO9=C(>wN0*yu)MxzX)QHIea*#nvV>Al$#6DZr9N7<$0C|k;*
z)B!_(Qg5O=p;3vuA8{*XU5~P^)5>a!g8!valqKCkDQ-g}`x&Fu_8N}5N<djq9uLx&
z>oS(&>to#6d2y5~6;MDqOu8ZwC$~qGL{Yy)++$)WQ5r?+8GO8#^HLf|<E&$J<4Q;C
z#z|Saq<-X8t!D#{XLDM^DSktYTW%8$bCI5srC6PsP7$9sAr5_~6!|Zp(0(E3L(X%Y
zoM*L9WS1t;SYNSjthGotw)8UHSm{z-`oM_(lwQ&PDTsG6pR<6*TIbW)(s?vis;9KU
z)P957Ryxk3j)qAS{e9+=I2z@fOrxwNG^(_iMoE)MJ-<<{lS(yQy3%(^b&n#gZe(}1
zQWvE5MJxJZlhzmc4F{!-&_3pPtv`qLQ^$fz9xK*FjFosw_F#NnGn@*1>AC`Iny#QU
zRaYP->a<>`)+N{V#c`Bwz6yDng1nSaVE^jm@?D^Rs%6*eU;^d=v}I`D;Wm|0qcQgL
z(7(pY<;}hfaaj{}qs<Ar(WUXa(FkX72b|AwVhWO?brkKB6qZWLoZ!<_yj8eI#L@^~
z6w0or1T*TG{nY%(ZFec!?h>?JKH6@A)^<iNTS^?IGzE=YInQC|HMxE<55X<ZMcMI`
z?Te#qoR^LBvf-ayf;!2Jqs%Qqb3{=z($v~1V%!1qoIHoZ%sdWaDXT=8E3z+5!2B_W
za-@u~b7r?XF9%RdSJcUn<8eBQtB)>{5~3)<6XoWaYe-o``&w=y<@!=67xR2B=J;HX
zzh`Of7K!mXp0`8#Z9rPwo|tDx)F%$8r4#3xQbd@JLP^g1%KNdVg!Rl+-f7p)3yD)b
z|8<=Eci8iR=X-Rf?1BUuX&sGuKL_)E8jToC8A$FQ-kX5_c&u0n`!*`q_aM<*BJ4eZ
z>E%RIPGweahC9764gE(=FK;W+8?f_gdPx!`RR(Z`L?gnvL)vUe{j+~6{L?XqY)Ntt
zZ%i!Wc~**(D6UDgtK83$(9eb|*R2icXKEXSJH~1Av1Eyon?zm$-GSF!t)8Q_KC71b
z=^qgN29`Zblv%#^4ac<)#N3oq5^(KP<H^Pt^1-bBaiqrg%%enyVUyMP)H;#JUoyfZ
zVf+n8xJ1g{fbkc}|3cSbE}KuxC-QtZoaZLYcbNB(=h5}zdVFz0O?GJ}rB@jaBo4+8
z#P-MZV#GA6*Lr^7AdO3;alTAk2Qz3~X*!KNkTW=PAge#4H!bqmQ0pN*meMiTreo}<
z<NB6fpCZ=-mwfIsM9X0_MLn#9ULWAr`UbAC*&9;bDMc8IYQIw3Q0*TH5+yW=bx%{6
z<Czj=Rt8Y*mx2A$Bl_ci8cT+{FGBtG9who5?4({#7xDaYG#=NOIP{6wpkq>I3}vEU
zWR~#SBH7JhJAcccxyJoc6v7t7Q^CnjG9Fw-S-vhZSnr_N(%Xa1k%nW<CatEJfv8^Y
zI}zqvQ%WM%S@l{k)8@TLUnSann5ac<dkQm0X!C27Ki<GG9CGarJHB%;UaiR(FG-Z+
zpX0T${=pxJqF*Ok71{@bX^P|WV%!YJ+{$ZIE&Kq=@kgR<2+!2=lBmP_GKI1y@fu+t
z#^BR5TKc)NHW6cOAUY(^f#uQWxZwMOPZ)ng>rPxBQc`0nbwko1u1Ni{y)o|S#;787
zT-tbV<YSokL><`PA=>he-z{-i%zdd?pQhF;t1IRDJ_6VGR9xS2ue2*<J``n9>#1&#
zpS=;i7F^Fm&bt-o+y?tF^t_Pk<_?^*3zjxp7x_Lev`>cim!Nq#%sMFaybIOwkacXB
zwMVFbm^|~Ea*XeB8e`o}V@e;RF;b7V-g8F>)Q8+}hROX#t&_25iB`Zan;lShm?N@t
zW66LvGI;!}Esk%BdM#QV5HHuoKHRhUK+GN4=y%!m>HTUNhwy)`B+(2t4Yh7Ayxtnp
zepS&DEr8iV;}Pcvmor8yC!akbMxr8ErwCgE?SOTwaSfdZiiX5HA_{A?VKky7S&Mf+
zt|cGBek$Tgh?S@k_WRip<_1Fy=Ef8<?279h=EfQ?bX^#lW|EGQO5(L~SB7g?BkYuj
z|7Pedunbdh-@i~@6eUV@6|7i<F+tCR%~9jXjHb+{5$@E+6qBJy?dxb?%q!Vut$lly
z_KQRNVGUo8_LJ8dm@~2d$iey}r`|tzW>3JJB;``*U57lUAx-7E$V|$WD#NbvIpfNG
z`pB^N>AiG;F&R=PLH!nMo^0G>W@EjBGRb$&Tu=NNxT28~-2iildU+c9vQZMP6YhAp
z>)_sL3h9sPeOO@v?!o5JD6<LoVY6``HiMMf5clS~3topVaL*JlW_V8JxwIa4^xAWh
z_@VbQpEg`*e~nb9pVdFYy`6R)&y1x^Um|6qEi%y-nI5d;jEQ8#STULnWW=@D=+V}c
zZb@D<!ceAkq>)~d(i^tELgt8+q2aW7>WjGN`6}#LQ<ym+Qn@0vkvYsYBxt^j3tjsU
zsQLH~*5f~hy%}jt%Xz^%-zwJm9)G{(5RYFj(GeKUQNk~R{`d-sjB`S+k3sA1SZ&?S
zdHBhd_&-IWFNiSNW3iriBmA>IdHtV*dvsqG#(pNoeg^K<Q%QXeHKgzH9OxT{>kC+t
z*el<Qmqh#fTo(EV>NE>=n1yrDS0dKGGH+NhZ!8QuZ}jPgUMEsU$NQh{1U%PK%f9J4
ziOh8pt(g-rt}k?NAL+g(WPViRUoumo*I?gM<Nt)~U~s!92F_Q5f$ctFwnR%{dqlkN
z&O#aBJ{H>UVeVfK;=I>j`g-*mfbodDXPQBri*^6dIVALcJ;HTeX`_W`BU9Kma_cTo
z2cJjW;nw&mF_eNjOex{#9r2B7J&ao}(KOiT`k*>1%Dy6j#+QCYKVDj_50{Q{RLHo(
zx)*bcc%I^q|7*8mtpK|sF#bsQYT+1eo>c25Bc3wMNtA&(A)`KZP@UC3sXyLyyF~Zg
zA<=9#Ps8|`?Y+vptXjBdC*k}?#nULC0oT1`T=SA>)P@Mp$2d>*qJ5h#u*Qfq#zOi*
zT*y6v^i7GPzo}@M;&|wJQ*msDIB!P4d7t!5?MFEGW7v}VfH`95x?jw(m|s!vp>ym9
z-;yZq+Y+s=4;i1CXjjy0X6bOstoJ{aQ^S3CqeK_qjW$!mg}W#Cq_t9n>+z5^xZExQ
z>-rM^wTAQZ+IJ<|dJpED(0T|QbCI-qpB&OAnVZojuztkJ98$O8uDPG^-q;17=gV!J
zpuC4ruRi1Ebs^?jvzTi=I8S}gC}54ItzW|4r-kGpoL_`{odw^Q$O?Nuuy52~_+E?8
z+<`DxJ}lAj263MQy)eN2Y2(?3FlCQOlrO@~fc|oTJG*!|<yuEku6ZQp-)zjkxTn`v
z`$|9S3O6o>tdWP;<oZ%EU!<VF8)!sT(uVlK*nyaS<rxvjQ>)c)3hLKD$p<j+7Y@HX
zSIje+m}f>zPV$6?E7HOxBU}<C?@AE!6XqqXokn5K8s!o5leo_svUXGN&*Xa>1J*Y~
zp9^VW<@W~+sF&pM?+=7|E|5J!na|{F2<P`g{Zt*B0r&CQdBZ5xI+9Y+2B}g8CAj_X
zi75RIeGS*}G@P%@3pn4LgY!q>{4DK!`Mo*$J=IY(9R4u@{t>T5VV?1*;~}(M*<*0c
z*)a6pul7Rm2aWeI&-AtRK)^ky+<q9xrEvjcIm~q>+#JGvV_!<}^=ns}DSR6Io@0Xi
zo@3D56Y2SpHlOM#yE5G!_<qSx-;wA8Sd}_PL!Uc`K95%2>9~Kvcu3zRzZc@a#>&qa
zhGTu5NQMoG?jiaASFN)H-%t35)A){*E3K2lr7=G-pdKTv-(JR8%uLqN616VFt^F>r
zKAU6E(QU9bf%9ePXD@=*(ZTD2FQp=F*k(1n+^3R*=bR1bQ|fyT5$1n)nvQOUJ)xCD
z%tQQnl6@m6)t5r42NFzDUOeSt?B~5daxiNkqd%=T)t%C4FlqCAnx4{D8{EkO{mnN9
z`GdU_dQFha7*FvY|MS5cb9K}S%QcI0Q8xxpqJP~cmk;H_nsgMdp`#EUbE+w#e@-0b
zY{7lYKuUjd?{IfwV?t59DWD!W9Im^g&_1I|G6$8_MzWS4_e33Sfwijn`3%>Qa_xFB
z9PO6K^HIn=lAwK#Knkb}W$sVG+;12<_kX&3!J>%u5$Rdzr(E|AIUZrY%g&3xJY#ao
zFGKI(<#846Iw{|07_bgiuCJN93?cWSB8@O>!UwL^(X+5sfo-%p;My1d8aEhljl&u^
zR@~#nY416y@kdwcXd>+1kUkLc{iF-sk5%dDr?A~>T<Yg7BCY4Oe8msR*KccdbQboT
zz<f1@nS<&t{OplK-!(%=W3SWETOzJW(A5F%A<qWF+}rV)FC)yyFlnw5ZVPl`osNp<
z20nWT==UMl-T|Zf>m)?y#_P${sH5_W{C<7;dg_7Sp*+8z^-?|6T6J_fAnbAYwc@<>
z0e<TzLc^~wAnXs}Hw9sP#{2yS;b*kzDAH@jMCIIA!7RaB1f7D51SbeyEqLlue_H%{
zL!{k5KG^@);=CsXe<-*`aD(7#LAT(ckoad^Eb=4h5L_wPC3uhE4+VcNctG$C!S@AY
z@)eF0oFrH&Xck-|c#Gh71a}EuU97M`uw2BSB;4nU6uu*PSa6@<F2T)$cM9GlXcaUI
zUM*-8j1fF}nZl!j`vspAd{}UkV3(j<uvYMD!AYVVV+GR$qttqwpzw%bzu-tw?w<>H
zk6^c8m*7o;4#DdMrwSGc<_IPVmWce#5b0zI_qj<TUxG&k&xn4TRv_w4ut2a{@JoU#
z1=k7QBe+ZO*Mdg`PY9kBG!!ZvBREO0T(DknvEXXKZo%DxeS&WYo)U}^<;oGfLa<V>
zPH?{962T6^+XdGPZWr7o*eCePq~LLQ-^5_|py>Zc1z!=IFUIc-!BW9|!7Ra8!84aA
zJT7=xuut$w!G{Gm3EnBVOmK<d^@5dxC4!d<juA`}Jewa}uKl8%1A<S6l>0U^d0cLr
z$G4)P)n@Zm@W-*M_`8s{Ab*pk#fD>5vu9SB>n4{>vN{|@oly<Sr^{<R9{#%DN)fW!
z?y)udT%HvOaa@{a^G&roY!yDA$KKTLvw7jWKtI#szTR$IK6^1AqpyjO?7$JX-PuyX
zdy7;#TwYtP#c9Pk9h@4UV79l}=onSIywwQ8F0)JXkJU{J%2QWB%oX%z)Lds<P|A(b
z4zJJSusM;QT|cwk;j>q+K&-BLcB`$b)#9N#o%rm1h1Kej_f)-BU+1z|>+DS)i(JNo
zRBdzE)HI&f*V`;sIdbx;c{aK1P9JY7`GHy=4g58=R%xooW)m^dMn1d2XB7perJQnr
zdj`^Nwm3KlHA_?FkNS(x?IRao<Fn1SHaEvU%hJYvcd33Ri?0>#QS6@SvbH-!t-|jS
z_PgHZ@!DO^nvM#}h^n`F+fk{ls5mQnCg;lJvAaBW--;@Sg=^p$E=5(l$76G9Th0DD
zz)uakox>^)H3r)1?_V4aImq_sDj@HA<fF~CET}hCDs5q3ri5RNv!^@JX={i2H}E$T
zxfhKb8v8t=>uTy})o49ARp<ZyLj_He+sM_-Umd(!zufCaOuoexbhi$rsdltX_u1Oa
zwhp9wGkly@e;e3!_2>x}udTX@#&Im*6*`m6w$y*l52@Pjbz6MRt$x2Kz1d@NdL78V
z@WhNvHBPI^f~5IV$=nm<Eoo-jP_Sm?EQu;@Eq13k<6iW^#maX%<i3hp)z5WX5ux~k
z2feLpXmu@DU2UQj6gRi~Y~;PgjwC#CBg<o#`#GA_>+)1ku5PAF`??5qbB+{8J(`EE
z(wSV|fZn*6kDDyq8EBbYOQPbmQ!bI~NAV>32G^~U6wPg@saIR(3UyAIUTODX=n}mp
zMw{}L7xXa>$<0yaa`1@XqhrVHcAm>_<ztmg(fT%84(&h)HC&lGGq|`1nT4yt<*-{T
zJ?;EW%4yZpMK&B<H*?x;o)y<y9PM1bS?p8eY;U{Hwt{P~h~4sZ1rKTqKA%s(Vi$h1
z-8QEP@l|#sd;T+~$Z^Qo5a(ROK1y-nQ$f@GZjq6S>gy{^YBp(%gxT8RsOhjZW2UX3
zhT57swUfK4$~D(%ciMe+i}D@WH25gVO?P^I7H2d3D&aRh;2f93Cc<~>v_8l)ZG~5x
zbAP9+wOQQqv_vn<y29abHPb{%J;m(u2gJ22z+ais)R_LJ&W-`!WU5@D&VS$3^GM<W
zY&F^4Haer8ZC(M_`xr0Wc`?b@5od!<-nohB3;H^nWtnY=|8L~}CwBl&3H85Oo-L6N
z&F8ROntGd3o-v`bC+3k{DR8^(=Xt+hbT)OmUnkFr$`_96>`t`E5@k-eczo?{F62CV
zA<As?wAr2hDG{qAT&G&)B&;<Z%{DjZ7zLHDAdm5!8u74BKiykxYie(4v3X3mT=Dpu
z5LNGUTyIB`7ROxYayxH@kjs8`F4xj_x4Ov{s*m?C&f+6g7Pqh6W2?g0z+j>isN2AD
z8Kc$9quHfhGwFV{{ua8Nfh~yDnp_Vi_9G{2`Ri`i^7*rCX0lF1zR{)a)+$bhC%}c$
zREKw(7$0;4yVTJ_3)m%hxaWslb*7_q^ZB^aX!Luz4G}abM-K;F^>FttpQDTgto>wH
zwG9gc*9t0)o`<d@UpvUgbzkFLX7{+9ZAkHYi^pzh;*MaAaHN8IqZ%AGo14aSy(-mT
z(?NgH`A1nu*Q{__8?XYZa<!xF4v5(<wEIqwrdoU!M}^zX{jY*na{5ZIR01k9G#b#0
zA<Q+rE5I#Ji1Y;xqn?39kP_TO6@lv;^zlS_O~nJ%rgT|qk=HE&#Z|7h-g)##P9@v{
z`o3PC5KT6Z+zgm)<cuP{dz60Dyj0};*`ccoH0ovgx+<&<l?dnYyZiD;Vi%8p_&lf+
z--g7c(1KoBS2ewwC_Xs%7}co~Pu$9kq#WR@U6R@Z6)(CGeF&EUZuiRe#f#BZh$bt2
zLDbniI->L~d8V{t=4^B0il^o9n*nRjd1O&~h*c~+X^y(CMDaKl`j2>>7q14_V&8HU
zcF|0Gv&ZFyYmu@V@GcT9CkEG|x$@U6Ee>TL1!JpD{yuD$pdrK^8{#Ix!4`!_TSAV9
zywv#nTY}?P-T!R->hG`a5$CCU{p}ELb$`iC!maK<*`ek~xEs}Ugu7O_hY9x-;m%ac
zCEV(hFoSTXsTmXQ!Nm$YN6KI2qtG%O635H4!yoSx$JdQkjz@(Y-y@Ef&I%vCTO99*
zaD2Tuo<BPL@qe^msjrePuNQuIBChT?8V@W!c>a9nI(<+(s_vj&y$tk;FI||te$x4o
zpIZLk>;GQ=_xdz1bqw(}C(u9Ct-d<l$~YbTt8(1bVE(7lH&y?0;d`bk^Ua_j8(%}z
z`9Uwp^DJKrqKSX=LEZ;f-iMX12fPn!tYDmAyf~H+a4bWFWhRSw4Z@H2QBD=c{uc@V
z%Z0l_xT}QQB*N4S{|3Q%BK`#;ZHMsZ-ySl;cp1ZcD0@U+W5oHLBG0P?SBv9o#PPKv
z9{#vL?+ARKa6cgOvsI+`knsPp$lnX%`0vE=tYJ!BsCD+QeHkLYl;g&TuYY;g{w=S_
zn?)+eBX4H--<$8x^Yy>I4}`q_!@ERJt(d2EQS$V$6%%yh>J_Um634f{uJD0v3Rm4f
zAaB`TeoLp!9XS6N4<3Hd7$N>u=M<I;KK?1suMm6!=fAk+=$4W6=9XhyZipQJEf2_Q
z4lXEj4u9#ek-mI*$>EZr=f8aDwSf<bif>Uup2KyuD&TmZ_+6!ag1v%!1@{Q<7VHt+
zA=oXrNw7<>L(n1ED7Z*)zMxsqBv>a{D_AX9E?6cwMX*$`NHAY8OE6I|DkT212Nj+a
zJT5pOcu25MaF1Y*;7-AA!7jlL!MLd4`w6%3Zxw75oG)k+tQ9O5ED|&d8U#<jEb=SZ
zFStjrM{tK=x8Nqh^@3f3oq`>LZoyW;M#1@lwSq;0MnOYJ{3JMdK;&1jPjHW5w_umx
zN<p7squ>-lqu^(`XZvSf>hF&IPd)xgb<sOFeE7kmqW%9Y+UqZ(y+nFFrVxtxQ(d2*
zd@KBOlu*A<YNyZp{LdQxUmgEv4gbIOr9OY`9Qsp5|I};#8)DviBe=g#nIz_w&xxFO
z{!JPF`9EIq@bAO?7wmE3&k+#)4VHovi(s>0ZIZv-9PVmT?!A_i{97Wl8}>5neV75~
zUIv>9bBJ)0gsJ1ZN3i27pAT;puiYZvm0$VCsLnI*zvZ8|C(-|Z<KUBizd#rA(u<cG
zUVR>NPK<+p=QWOBdAO_jfbVQR)X{11cq&mEH1pgDq5^1sD7_#PYcXgCI3f$zXXrF=
z2FwlJ2yTIOKyL-F$tGG0T@Kzl66+A?PH@6#^jYZq(eg8C>Vw`4_G2*gLo;1Q<OA9b
zCXB;c?u?ES!RPa^-h<u`{$M=Tif8z<r9=;1jQl|F09WNBUFfyo_)Cd4L+69<!Fr$v
z!RCor|3X{A4-1ek^jWa15d9XK`6xQ;X^tPfs0d^6BOT>|Ps56!_kq7HMjoIKfzMCI
zoCCcd99xQd;Pb(6z}7<V2FG57GP6H8?Hc3<x)wYDI|zLcEGfseAG#Fu!J^LU6joqu
z3(e%uE95})CxA}F3ZSzpF?V30Uk1(mMK#vi(EF<Sv&>l6K_3FsrecgjGfl9y(9EW3
z$Q!g3eBnCO8+1QdI1_aRT{KgEkARLsGnesptsm?7eF1tB77M)>EUZUeK^K8r8<00>
z=C<pRH|QPU6Z3Fi0KEsi6O*PDdL1|cw?7W(a&Y>WP=4q-@Kp=)34Id0xCwm;Iv-4K
z#yXPI0&j1@oCtjyyt5VSd5#mj(T=_ZJ;+>w`v7R><W;DPb0`bA237#Q7PPKLIiUx@
zSJvSA0nHDyR^Nv9g+2;CawqycbkWy|Mt%eNVGUaOyIRo9XJK2R`7`-%!gg{z8;I`y
zHrBw<J>ZM{V@}ZhptKS73>^iI!3AVLv=Owy4nenqzk(g(I6?a+^i|F?_})F}2hf9{
z@m`ecyiR5%EC;$8Tn@{F?f}0BD}e3>{{Sn69sr9rqm7`MZ^5dekAbcCqc1@-?}53Y
zH-jnNC_8i-_{@VC%g}w`sjd91PEV)7^lkjzJw0WCmv7@eXY@1$T(ph9!=abCVjF*V
zOiwGp?rr?6QcpX;uI>DN2EEKjVU5r|;E0EKp8`Flfi(~DGfF-6gJX8^^;b_u@TDF6
zj8abr!N2a{{Verl_&$;6VdNQlGuZM7`Z;tfc*`UF{Wv{!f)jS4jiB?v8)5sQ8^Lew
z<ZHT~HiFOX<WF?zX+QWe>?l9^{Q=QUu;W}7@QokvWUi;9pz~4GKeQV>537YX^x*mq
z^FbGZtv|xp<a5Bsb`foa-V5IU1o{u3^JCm+?m<06CxSIUM}DAd!JenlAE23?zd|3-
z>1EyrD}wF@pZpbnCJAwZhkwO;mgwmS_@ie~=g__2h-WbmK&OGbVH=^DFFnh9$f7>M
z)36=T%=A9qe-`ZvUfsu^*3wfMcq6PAx)EI4$KNT_%X|RV54{z94t5B7KloSJ0Q5;P
z{W<h=juX5JHpp>;TVSU-EO_y+QMb5^=7V>^VxiZ8@z3*C#;70A1<QhVgC}4{=s~by
zKdvLtX7B*42>Kv+^>0u=&}HCLuyW{LaNG-MFX%jQ3(N$)6&&^=`X_WOcsr~Sx(hr3
zYvr^+Z$J7cv=2NC>wrE2e)%P|6Lcf^46F;f54`RG`YLoC_$X`>bPqTQi^6W`YVap7
z^K&V*Blt&H5A;!R0T!HlpcjE_VZEIHfPLSXH~$uO4$b^3tPeVHUpMBGL+C%y%;m5s
z=nn9@S5Xhpb>L%%QC>bDyzTeMKXe!P3)nj7ec)l(M(C5^l}9kvpr?RKVOyac;5Jwf
z^bYVf*k0%XFyjxH523TbXJPxH`@ngxqn|*p2cLi$qV==~JPON#E*e0;`XkyGdO!FY
z%mh6EUiv4r7j!9j>rvz%x)b~<tdq|NKZdP?rZ-Usu#M2nHL%UlYr!qB9_X#$%dkDr
z2f^d8UTCKD7U~u{3e1M}L+60SutS^&aMwHN=g_;~;eF*XKg8%|77wB=pi4m)Y(BIb
z+z9K0X6}RSfM#yQO-3L4gRjDlKpz1UP9jgxiQpGtC!x#0rLeQm4sh(>(Z0hlcEFvm
z9BAf#SROPp^%Slb&}rZ-SShp#ybD&wVZmo$)zE#Q{sW>q=qPaOX<R#?neV`?(8s~z
z4{@!BZUs-mRzfq6euVxH&Ajg{>K3{iEczJbf@W?zkMp2+fT#JXM66!sE0RP8=tKNr
z0G&kT&_#NQz8@t~E%Z)MA1%>*=qPXntQC4I_#CVYdOx^|_Y#9%3&swU=pZz+05$;4
z{Bo>Br=c6c`(Ooe7^h%yoJ6J2rC?6HL}kz>a1E>udM)@a%nWTzkmwnh1G=A?DA7*n
z4)71K0q6m6eUe0j(98+R$WuJ#Jy61pWfpW4SUv*jLNjM(Abw~Q=*^O-1KI~Zk}c6D
z=-r@ultg{d%x7R(31~a;EtnBHCkOR2MxuP^mEcpbIz9)?%tidrMPPF-?_s7VD|iyt
z3eCLPDA7s|3tpKg(K=`|_&eBE=tJO_$4j&ux)D5^FVQ||=8cy~bcFrEF4$4%O<=~Q
z5*>%m0`G-ICE_{`UNjMP4V?$>gq1?~f~^G-nV{E#?n2}ln!iiej+^RM*5Fy#T4?4P
z+@x>fIKiL5c0%t3w-igX7kVqmd+8p7X09wjnW34NOqM8dIQj<o3d{(72&^lWs0f<*
zS6Df8&gBy2T`5r~G&6S!+5*}LzH>F=ggy@b{2GbUl28_K^0kNux)hB5BI1FL1-}S0
zL6?KK!{$SGfv>_Ep^t!Ta0l*#-T}s6hqi!D1WRB&94FXOCs8jn^Iq5>^k(oW*lErK
z_$L@8V{C%MW}+WL$AS}K2IvBCHY^L;1fGHAL!SlPXCa@^9pFA#BQ&#THqwS>{tea(
z%^ReDeGb|kdObLy9(jN+1sB3%4Y<aEt6&D`wcvljvY<DCM_@*1rl}eA2F-j6W`;fn
zCRru2LL0!#VGaX-S5cztU_R(Na6N1#H1nf&)Eo3!aL+R28=86Za+C*pCHNTZIP`8X
z?N+oSH1lsTV+zIvcvUC*1auj=24;rdybA3PTLjHK3A6I~pm8<w2F<L4`JlVNFRek|
zpc}#Mu=UV8z%Sj7KFt2$kMBTUq5Hv;umNc5LOsEbah%{qu;b80U@>eEx)l5~>?HJQ
z@Q$yc&Qnou;0t$RtU>pK(p?y9&{5!3urlZ}@L^ar^iJ@XusY~{VB*(NUTEf}Fb8x2
zcq7aQ-3Z<dTM4}heCHb&o6yI><Znu}5!wJ2!gfO!fpcMdq346`up=B6ELe|nL3e=b
zU}+=tGP_|p(99dZjq{)z!3}q#PeN}7yT60_<a5BAH(|blUJG`77i|sA{3+}R^j`3p
zdyp1%ANV>fCk^v9m~}74IW*G*D}rX;4l9E;Y?f#a%mi%)?}oXdH-SB{4vvTQeV8kt
z_kq{kk9vSE2i@JsD|81qZ42rIx)!|lL5ceL9I$jN+8vttHtaNX^)_4ww`2ZHM;`$D
zVFqaCR~|xLLHocfcOd`JQ^3UUOEd+Vxg2JO?f}~#MtiXaZ+Zmv1HBU5{3znj(93)U
zRsek*9M^+9LFa)tz{;Q(fg54f&^_QykD<>%uLM(fqi;Z`fu6_FAE15U`>>tRC&A`l
zpbtP>!TMj}dJSy`--R84E_eoG0(KmlNzb8gXYf6RL{oo_vS*^+z_Tx)&7hg3FT$Y9
zz~!(q=#}7UST!{Bu72bbdL20KC5&I_Ja8(kgX09>fptPV4xp`HMqh+BgGXUIGW9Zl
z^a}D1y&HVvcM=_9e=zS5#v{iG?uDI(W?uZN#5XpS4@Mus*n~EMZ^Mkx$G}y8KzQi2
z;JVjQ7HDS50Qwtr8u%yJeCVU#>_4LaLz}?$uvX|zVDB5~XV6UZpHTnM%;#Y1p!b8{
zI*Rs&-UvSTCfbGl!8vcCKS2AyWpATg(4F8Q8#MEcV-kP0iH?G|{26Tl-3i9LgK-2M
z3%0-tpj*Kae?eP7r-7Hl8lk6v3t+9#i@-H7H}qO?8>|C*2lyha6WVYbedJxNEufjl
zV4I+s%_lIXpsnB@SPwMw9IO|b-b1-yeb8p`BiIq>v*1H#(62ZxP<Ix6X(Z-Ga0V<7
zx(@svtY9RsX)$I$#&sOp4BC@)G#}alRwe6bEp#>bDr_h85%4L4j(VYc!4WAsIu4x%
z=BDcCB(xE{6&5=RbqM}_gpLZJDNRQUVAarzz;D3J(CfidFduYcx{elR=x8PMN^sm5
z9j$}T13!fAggy(tkgKCZ(EVVFQAcN?)4-{)s2ucpaMD;EB|=XDH;vO#9&|VO^rbo~
zgWd<eK2b-Dpa(!>0n&ml12-1JpqXc2d!f&Qmrv5se&}lODcC{iO+`8yUaX^|&{^PD
zC+lbsx)bbyC63n1JOE3BJ~)|wTSG@V(6L}4EDyQ}{0gi9dN26Y<vJ>b?gf*t&`}+<
z0hF#py+KET6Jb{90&o`00c`@CVLoUpxNr*U6M7MND{LKfC-`mHM(B;;)mQQJZ(P5a
zWjfjo%^ZIX$_SkgPP`Ungsukv0;4f#qc7^{$FL&kJ>X&3lrg;CL;4juDu-q^S0bO#
zR&WJuJ%<HTtI#&kY2e>rJ<un?Usmhr2=qQMss`yoGbh82LzjY+rlQ@UV{3I(1Iy1v
zyMsT16+!O?yLgWaXl8yL$_vemo{4sZHh}A4N1&NoU`L_(cQE+3En3ej<lm}Te_kQ~
z21W0Ah01R;=w<TnDx8L9@^1;0OM02Sw|uQmFO&CopC6@{$$PaQf@bm_=SJLvGkI_F
z0ca-gKW@T3GL!ch?|^3V-rC*JOx^>#2lt^&-p4u?_k&E{m%0&}c`s}eH1AE#`%15e
zX1)R&gyy}adH?5Q(99GQ+6(u3yazPz>AVP<SqIw+&HFm@9>$xYnRmc?aUaL~7(WEt
z1I>FF^S-w|&`jQYb~o-*<UMHTpr7FWf;kG-!S@{??+xpQX7XOCz0geF*K`lo|1x1l
ztizcDd_FYqN6Pz*wn8&`ugyKsOx_oBFV?_J-j6a1>q#c>v)BX8<oyzlVx7k1JrMJ-
zj$-m2g!7@9FYr0gyoVs~p;wDF43qc4tHXMO$$Q!Ppqae))?R2P?;TZ&Iho1(K&`|)
z$>cqq_CPb`eVH(iF?k=QI%p>Ele86@$$J#_Lo<0V95dz_ChuEw8e@~m`_3%FxM1?W
zF*~4{ywA%?XeRI7Qigucyacumn)h$1gRRZghk1wY-}6$w7owNnkId)CVI=Rd)XAS=
zb}JwE4LDCC{vpz?A^uUszi-zKfBqct9*{pr+z;~k$HK%TDH!6<udIMKLREkMoK=1V
z|0xmtYa{s2kKo@5|Bhc^ev2nMD!wq&`8>w3<aZl?gLzTscjvvRr-cFT&X+J>1f0L|
z0FDQ^QO9x}7Rae8p51XY<i#JmdHe0R>9yBhqgP&eg?{$4pV8X2YpJrblCHYyDk?56
zrb{llgtD@-_>-%cgJOwZJ5Sr1mGCG2vDg2WaPOTzF8~qn|NIdD^FiU@KBoX8;Qvs6
z*{z0qPjRbB1ieE21H+wv;wg5Ye`5Rgr_RIi1pj9p?7y8681AF`8#t;n^%a#1@qa=6
zLRsIy{^w4-Cu{a$1cp0hoWS9>8Hmy!-DXHL@PDES&%poO3E6*w{GTQSg*#I|1HnF;
zQ(RF|yl@WsCjYOfSQy~HQ2uY25uBbO9a%qROy9oEkdEZi_`j|F_U(oYgga%FwR|)q
zB)t@rjAK7jKCOHz{-(jFqH?~R-Wg4QSRR@lvYyVdBW3Z&qXm2v)hKkv`Ecn~6wepw
z)z;5f(lg-y!ZUCwdd8VKL&}$Cvl_-5l=5Y4-!7NW%BiN`4Sz*LQn>WwIuxna3Y}7{
z`t$$d(DaO?v>cZbZHD#~I?bT^^Z&Gv@_oczLFpq4xsSkvUQn<4<A3G6kor`*-Z`~T
zp5uNg^b_0FzRLeY(nE(oi=M6xId-YWQ2udC2B)Wu2*LC6fKW^w7i_1D6GFl%Jzu3L
zV?a4Ickp;Wp~j@}lv1Yw^{TkJ?7`ubo_}8938kk7wxi-!r<Z{A<eq>2oJ{%uDYdsM
zeaPQ8!=)!;^`|#Y{qOIaA?c;dZM2O?fSQ&_iBrwkruEH`^it$Lfx*5|O;3yhIlXCf
ziX)^){<%e@r;fT)Vh)Iup4{gb%qhMhOnNEBTHhQ}J}wO>IU!8>l-VF$dehH-C~9v;
znEFgvc;<rX$?e8!`}UyrOMmp#1<EJJdVPf<rE-3l{t_lVdBiLIFr77xHw^8cL>}
zUg`4-q2UNK9!?AiCoVS1SZBTcso-&cP8$nB;j~L!;KeRfpBNhMGrs;~!b#-+-eWoS
z)+#H(o26Yc8^}gJYL~x-Cx3lKnh8H6%!1XUS5`LikewC>FB?r?rFi;)(&(6;*O05p
zOszPJL(BtjLU<2Z;aU!t1Aj)4WAx#O;vO%b?Sik7R)FmYVWeiLHsq%rXL8Du;8%t7
z?KrX&dKP>;aLfpEiSwP%9D6&Caf}{0440r8J`U|%4rv!*Tjg{8<qyvHq@cn^#rB}Q
zxhU@%`dBXSD*r*O$&>h29`)Bs50Vt6)W`Yr&XAS14skq)IAZ0S0X|2$BM5Gv*w3|n
z61DbekZZz*8sYN0QR7?(O6>=?j76?Z<%m*m?Ko<aKS}I=b;hnTJ%u~8$0>>43#}8a
zykDa$k#A+-ttL}Ys?|uvBXY!Twjv}2+$~BEAdW1QV+oy;(^&JF>s`s`_vCyIM$G4Z
zR2h)Z=@IMWlk<!wDzB7=Q3=x1$$9Fesd5cl<-V!(Jno^B<iBuz;PV`#r_gWJK5(IN
z)C6~noKOGgh}185Oio8?>bT`mZjnc!8XMQqGMsJ05p_g1BUV1EMUI=*3i+5n9?u2)
zqF1g5Wu&*jRUpUdFHt4VW)(VPe7cz+^5~a)5$f`LsLLe!i=Mx`?3H_f1N<`D#e+Co
z<T88lH<|cbbEXjA;?Q0q#>XkN>2c9Mk^5a}8UelTg5$tc5!$!(pJU(pAVS|#>s0CY
zd{t<bdsw^Net|Q~g=ggDk=o=_j(ug`&X?!y;{jv8%;NRh+L|0Ij2&$br}vt%?H=dV
z-sV<Yo5fq$rtTwB*z9V%+Tv}Sw5)invCZPNFSdEr%|xylTRf>~?6t$=jm9#cr`;>>
z74<1&o2-OGNUyEAowqm?Zuod?Uuj1wHs1YkncZP)v3a$Ffrph1+mMku+cKNO=-~g?
zjJ0^DJD0hZ+B{>8?e>ah-YDprv5PGZuWjtLWtT+=>`(l%$f=iI7L?br%d|qnU3QsT
UpVyiyOcmAD(`QYaKVOLd2Rp@=@&Et;
diff --git a/distutils2/command/wininst-10.0.exe b/distutils2/command/wininst-10.0.exe
new file mode 100644
index 0000000000000000000000000000000000000000..8ac6e19b8eeaf7642387123c749f416251c496ea
GIT binary patch
literal 190464
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
diff --git a/distutils2/compat.py b/distutils2/compat.py
--- a/distutils2/compat.py
+++ b/distutils2/compat.py
@@ -1,10 +1,10 @@
-""" distutils2.compat
+"""Compatibility helpers.
-Used to provide classes, variables and imports which can be used to
-support distutils2 across versions(2.x and 3.x)
+This module provides classes, variables and imports which are used to
+support distutils2 across Python 2.x and 3.x.
"""
-import logging
+from distutils2 import logger
# XXX Having two classes with the same name is not a good thing.
@@ -20,7 +20,6 @@
_CONVERT = False
_KLASS = object
-# marking public APIs
__all__ = ['Mixin2to3']
@@ -31,6 +30,7 @@
yet does nothing in particular with it.
"""
if _CONVERT:
+
def _run_2to3(self, files, doctests=[], fixers=[]):
""" Takes a list of files and doctests, and performs conversion
on those.
@@ -38,22 +38,20 @@
- Second, the doctests in `files` are converted.
- Thirdly, the doctests in `doctests` are converted.
"""
- # if additional fixers are present, use them
if fixers:
self.fixer_names = fixers
- # Convert the ".py" files.
- logging.info("Converting Python code")
+ logger.info('converting Python code')
_KLASS.run_2to3(self, files)
- # Convert the doctests in the ".py" files.
- logging.info("Converting doctests with '.py' files")
+ logger.info('converting doctests in Python files')
_KLASS.run_2to3(self, files, doctests_only=True)
if doctests != []:
- logging.info("Converting text files which contain doctests")
+ logger.info('converting doctest in text files')
_KLASS.run_2to3(self, doctests, doctests_only=True)
else:
# If run on Python 2.x, there is nothing to do.
+
def _run_2to3(self, files, doctests=[], fixers=[]):
pass
diff --git a/distutils2/compiler/__init__.py b/distutils2/compiler/__init__.py
--- a/distutils2/compiler/__init__.py
+++ b/distutils2/compiler/__init__.py
@@ -1,11 +1,26 @@
+"""Compiler abstraction model used by distutils2.
+
+An abstract base class is defined in the ccompiler submodule, and
+concrete implementations suitable for various platforms are defined in
+the other submodules. The extension module is also placed in this
+package.
+
+In general, code should not instantiate compiler classes directly but
+use the new_compiler and customize_compiler functions provided in this
+module.
+
+The compiler system has a registration API: get_default_compiler,
+set_compiler, show_compilers.
+"""
+
import os
import sys
import re
+import sysconfig
-from distutils2._backport import sysconfig
from distutils2.util import resolve_name
-from distutils2.errors import DistutilsPlatformError
-
+from distutils2.errors import PackagingPlatformError
+from distutils2 import logger
def customize_compiler(compiler):
"""Do any platform-specific customization of a CCompiler instance.
@@ -14,10 +29,10 @@
varies across Unices and is stored in Python's Makefile.
"""
if compiler.name == "unix":
- (cc, cxx, opt, cflags, ccshared, ldshared, so_ext, ar, ar_flags) = \
+ cc, cxx, opt, cflags, ccshared, ldshared, so_ext, ar, ar_flags = (
sysconfig.get_config_vars('CC', 'CXX', 'OPT', 'CFLAGS',
- 'CCSHARED', 'LDSHARED', 'SO', 'AR',
- 'ARFLAGS')
+ 'CCSHARED', 'LDSHARED', 'SO', 'AR',
+ 'ARFLAGS'))
if 'CC' in os.environ:
cc = os.environ['CC']
@@ -68,19 +83,16 @@
# patterns. Order is important; platform mappings are preferred over
# OS names.
_default_compilers = (
-
# Platform string mappings
# on a cygwin built python we can use gcc like an ordinary UNIXish
# compiler
('cygwin.*', 'unix'),
- ('os2emx', 'emx'),
# OS name mappings
('posix', 'unix'),
('nt', 'msvc'),
-
- )
+)
def get_default_compiler(osname=None, platform=None):
""" Determine the default compiler to use for the given platform.
@@ -101,17 +113,19 @@
if re.match(pattern, platform) is not None or \
re.match(pattern, osname) is not None:
return compiler
- # Default to Unix compiler
+ # Defaults to Unix compiler
return 'unix'
+# compiler mapping
+# XXX useful to expose them? (i.e. get_compiler_names)
_COMPILERS = {
'unix': 'distutils2.compiler.unixccompiler.UnixCCompiler',
'msvc': 'distutils2.compiler.msvccompiler.MSVCCompiler',
'cygwin': 'distutils2.compiler.cygwinccompiler.CygwinCCompiler',
'mingw32': 'distutils2.compiler.cygwinccompiler.Mingw32CCompiler',
- 'bcpp': 'distutils2.compilers.bcppcompiler.BCPPCompiler'}
-
+ 'bcpp': 'distutils2.compiler.bcppcompiler.BCPPCompiler',
+}
def set_compiler(location):
"""Add or change a compiler"""
@@ -127,8 +141,8 @@
from distutils2.fancy_getopt import FancyGetopt
compilers = []
- for name, cls in _COMPILERS.iteritems():
- if isinstance(cls, str):
+ for name, cls in _COMPILERS.items():
+ if isinstance(cls, basestring):
cls = resolve_name(cls)
_COMPILERS[name] = cls
@@ -139,7 +153,8 @@
pretty_printer.print_help("List of available compilers:")
-def new_compiler(plat=None, compiler=None, verbose=0, dry_run=0, force=0):
+def new_compiler(plat=None, compiler=None, verbose=0, dry_run=False,
+ force=False):
"""Generate an instance of some CCompiler subclass for the supplied
platform/compiler combination. 'plat' defaults to 'os.name'
(eg. 'posix', 'nt'), and 'compiler' defaults to the default compiler
@@ -162,9 +177,9 @@
msg = "don't know how to compile C/C++ code on platform '%s'" % plat
if compiler is not None:
msg = msg + " with '%s' compiler" % compiler
- raise DistutilsPlatformError(msg)
+ raise PackagingPlatformError(msg)
- if isinstance(cls, str):
+ if isinstance(cls, basestring):
cls = resolve_name(cls)
_COMPILERS[compiler] = cls
@@ -200,26 +215,24 @@
pp_opts = []
for macro in macros:
- if not (isinstance(macro, tuple) and
- 1 <= len (macro) <= 2):
- raise TypeError, \
- ("bad macro definition '%s': " +
- "each element of 'macros' list must be a 1- or 2-tuple") % \
- macro
+ if not isinstance(macro, tuple) and 1 <= len(macro) <= 2:
+ raise TypeError(
+ "bad macro definition '%s': each element of 'macros'"
+ "list must be a 1- or 2-tuple" % macro)
- if len (macro) == 1: # undefine this macro
- pp_opts.append ("-U%s" % macro[0])
- elif len (macro) == 2:
+ if len(macro) == 1: # undefine this macro
+ pp_opts.append("-U%s" % macro[0])
+ elif len(macro) == 2:
if macro[1] is None: # define with no explicit value
- pp_opts.append ("-D%s" % macro[0])
+ pp_opts.append("-D%s" % macro[0])
else:
# XXX *don't* need to be clever about quoting the
# macro value here, because we're going to avoid the
# shell at all costs when we spawn the command!
- pp_opts.append ("-D%s=%s" % macro)
+ pp_opts.append("-D%s=%s" % macro)
for dir in include_dirs:
- pp_opts.append ("-I%s" % dir)
+ pp_opts.append("-I%s" % dir)
return pp_opts
@@ -258,7 +271,7 @@
if lib_file is not None:
lib_opts.append(lib_file)
else:
- compiler.warn("no library file corresponding to "
+ logger.warning("no library file corresponding to "
"'%s' found (skipping)" % lib)
else:
lib_opts.append(compiler.library_option(lib))
diff --git a/distutils2/compiler/bcppcompiler.py b/distutils2/compiler/bcppcompiler.py
--- a/distutils2/compiler/bcppcompiler.py
+++ b/distutils2/compiler/bcppcompiler.py
@@ -1,8 +1,4 @@
-"""distutils.bcppcompiler
-
-Contains BorlandCCompiler, an implementation of the abstract CCompiler class
-for the Borland C++ compiler.
-"""
+"""CCompiler implementation for the Borland C++ compiler."""
# This implementation by Lyle Johnson, based on the original msvccompiler.py
# module and using the directions originally published by Gordon Williams.
@@ -10,10 +6,11 @@
# XXX looks like there's a LOT of overlap between these two classes:
# someone should sit down and factor out the common code as
# WindowsCCompiler! --GPW
-import os
-from distutils2.errors import (DistutilsExecError, CompileError, LibError,
- LinkError, UnknownFileError)
+import os, sys
+
+from distutils2.errors import (PackagingExecError, CompileError, LibError,
+ LinkError, UnknownFileError)
from distutils2.compiler.ccompiler import CCompiler
from distutils2.compiler import gen_preprocess_options
from distutils2.file_util import write_file
@@ -50,12 +47,8 @@
exe_extension = '.exe'
- def __init__ (self,
- verbose=0,
- dry_run=0,
- force=0):
-
- CCompiler.__init__ (self, verbose, dry_run, force)
+ def __init__(self, verbose=0, dry_run=False, force=False):
+ CCompiler.__init__(self, verbose, dry_run, force)
# These executables are assumed to all be in the path.
# Borland doesn't seem to use any special registry settings to
@@ -79,18 +72,18 @@
# -- Worker methods ------------------------------------------------
def compile(self, sources,
- output_dir=None, macros=None, include_dirs=None, debug=0,
+ output_dir=None, macros=None, include_dirs=None, debug=False,
extra_preargs=None, extra_postargs=None, depends=None):
macros, objects, extra_postargs, pp_opts, build = \
self._setup_compile(output_dir, macros, include_dirs, sources,
depends, extra_postargs)
compile_opts = extra_preargs or []
- compile_opts.append ('-c')
+ compile_opts.append('-c')
if debug:
- compile_opts.extend (self.compile_options_debug)
+ compile_opts.extend(self.compile_options_debug)
else:
- compile_opts.extend (self.compile_options)
+ compile_opts.extend(self.compile_options)
for obj in objects:
try:
@@ -110,9 +103,9 @@
if ext == '.rc':
# This needs to be compiled to a .res file -- do it now.
try:
- self.spawn (["brcc32", "-fo", obj, src])
- except DistutilsExecError, msg:
- raise CompileError, msg
+ self.spawn(["brcc32", "-fo", obj, src])
+ except PackagingExecError:
+ raise CompileError(sys.exc_info()[1])
continue # the 'for' loop
# The next two are both for the real compiler.
@@ -132,72 +125,53 @@
# Note that the source file names must appear at the end of
# the command line.
try:
- self.spawn ([self.cc] + compile_opts + pp_opts +
- [input_opt, output_opt] +
- extra_postargs + [src])
- except DistutilsExecError, msg:
- raise CompileError, msg
+ self.spawn([self.cc] + compile_opts + pp_opts +
+ [input_opt, output_opt] +
+ extra_postargs + [src])
+ except PackagingExecError:
+ raise CompileError(sys.exc_info()[1])
return objects
- # compile ()
+ def create_static_lib(self, objects, output_libname, output_dir=None,
+ debug=False, target_lang=None):
+ objects, output_dir = self._fix_object_args(objects, output_dir)
+ output_filename = \
+ self.library_filename(output_libname, output_dir=output_dir)
- def create_static_lib (self,
- objects,
- output_libname,
- output_dir=None,
- debug=0,
- target_lang=None):
-
- (objects, output_dir) = self._fix_object_args (objects, output_dir)
- output_filename = \
- self.library_filename (output_libname, output_dir=output_dir)
-
- if self._need_link (objects, output_filename):
+ if self._need_link(objects, output_filename):
lib_args = [output_filename, '/u'] + objects
if debug:
pass # XXX what goes here?
try:
- self.spawn ([self.lib] + lib_args)
- except DistutilsExecError, msg:
- raise LibError, msg
+ self.spawn([self.lib] + lib_args)
+ except PackagingExecError:
+ raise LibError(sys.exc_info()[1])
else:
logger.debug("skipping %s (up-to-date)", output_filename)
- # create_static_lib ()
-
- def link (self,
- target_desc,
- objects,
- output_filename,
- output_dir=None,
- libraries=None,
- library_dirs=None,
- runtime_library_dirs=None,
- export_symbols=None,
- debug=0,
- extra_preargs=None,
- extra_postargs=None,
- build_temp=None,
- target_lang=None):
+ def link(self, target_desc, objects, output_filename, output_dir=None,
+ libraries=None, library_dirs=None, runtime_library_dirs=None,
+ export_symbols=None, debug=False, extra_preargs=None,
+ extra_postargs=None, build_temp=None, target_lang=None):
# XXX this ignores 'build_temp'! should follow the lead of
# msvccompiler.py
- (objects, output_dir) = self._fix_object_args (objects, output_dir)
- (libraries, library_dirs, runtime_library_dirs) = \
- self._fix_lib_args (libraries, library_dirs, runtime_library_dirs)
+ objects, output_dir = self._fix_object_args(objects, output_dir)
+ libraries, library_dirs, runtime_library_dirs = \
+ self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)
if runtime_library_dirs:
logger.warning("don't know what to do with "
"'runtime_library_dirs': %r", runtime_library_dirs)
if output_dir is not None:
- output_filename = os.path.join (output_dir, output_filename)
+ output_filename = os.path.join(output_dir, output_filename)
- if self._need_link (objects, output_filename):
+ if self._need_link(objects, output_filename):
# Figure out linker args based on type of target.
if target_desc == CCompiler.EXECUTABLE:
@@ -218,10 +192,10 @@
if export_symbols is None:
def_file = ''
else:
- head, tail = os.path.split (output_filename)
- modname, ext = os.path.splitext (tail)
+ head, tail = os.path.split(output_filename)
+ modname, ext = os.path.splitext(tail)
temp_dir = os.path.dirname(objects[0]) # preserve tree structure
- def_file = os.path.join (temp_dir, '%s.def' % modname)
+ def_file = os.path.join(temp_dir, '%s.def' % modname)
contents = ['EXPORTS']
for sym in (export_symbols or []):
contents.append(' %s=_%s' % (sym, sym))
@@ -229,13 +203,13 @@
"writing %s" % def_file)
# Borland C++ has problems with '/' in paths
- objects2 = map(os.path.normpath, objects)
+ objects2 = [os.path.normpath(o) for o in objects]
# split objects in .obj and .res files
# Borland C++ needs them at different positions in the command line
objects = [startup_obj]
resources = []
for file in objects2:
- (base, ext) = os.path.splitext(os.path.normcase(file))
+ base, ext = os.path.splitext(os.path.normcase(file))
if ext == '.res':
resources.append(file)
else:
@@ -260,7 +234,7 @@
# them. Arghghh!. Apparently it works fine as coded...
# name of dll/exe file
- ld_args.extend([',',output_filename])
+ ld_args.extend((',',output_filename))
# no map file and start libraries
ld_args.append(',,')
@@ -276,11 +250,11 @@
ld_args.append(libfile)
# some default libraries
- ld_args.append ('import32')
- ld_args.append ('cw32mt')
+ ld_args.append('import32')
+ ld_args.append('cw32mt')
# def file for export symbols
- ld_args.extend([',',def_file])
+ ld_args.extend((',',def_file))
# add resource files
ld_args.append(',')
ld_args.extend(resources)
@@ -291,27 +265,25 @@
if extra_postargs:
ld_args.extend(extra_postargs)
- self.mkpath (os.path.dirname (output_filename))
+ self.mkpath(os.path.dirname(output_filename))
try:
- self.spawn ([self.linker] + ld_args)
- except DistutilsExecError, msg:
- raise LinkError, msg
+ self.spawn([self.linker] + ld_args)
+ except PackagingExecError:
+ raise LinkError(sys.exc_info()[1])
else:
logger.debug("skipping %s (up-to-date)", output_filename)
- # link ()
-
# -- Miscellaneous methods -----------------------------------------
- def find_library_file (self, dirs, lib, debug=0):
+ def find_library_file(self, dirs, lib, debug=False):
# List of effective library names to try, in order of preference:
# xxx_bcpp.lib is better than xxx.lib
# and xxx_d.lib is better than xxx.lib if debug is set
#
# The "_bcpp" suffix is to handle a Python installation for people
- # with multiple compilers (primarily Distutils hackers, I suspect
+ # with multiple compilers (primarily Packaging hackers, I suspect
# ;-). The idea is they'd have one static library for each
# compiler they care about, since (almost?) every Windows compiler
# seems to have a different format for static libraries.
@@ -331,43 +303,35 @@
return None
# overwrite the one from CCompiler to support rc and res-files
- def object_filenames (self,
- source_filenames,
- strip_dir=0,
- output_dir=''):
- if output_dir is None: output_dir = ''
+ def object_filenames(self, source_filenames, strip_dir=False,
+ output_dir=''):
+ if output_dir is None:
+ output_dir = ''
obj_names = []
for src_name in source_filenames:
# use normcase to make sure '.rc' is really '.rc' and not '.RC'
- (base, ext) = os.path.splitext (os.path.normcase(src_name))
+ base, ext = os.path.splitext(os.path.normcase(src_name))
if ext not in (self.src_extensions + ['.rc','.res']):
- raise UnknownFileError, \
- "unknown file type '%s' (from '%s')" % \
- (ext, src_name)
+ raise UnknownFileError("unknown file type '%s' (from '%s')" % \
+ (ext, src_name))
if strip_dir:
- base = os.path.basename (base)
+ base = os.path.basename(base)
if ext == '.res':
# these can go unchanged
- obj_names.append (os.path.join (output_dir, base + ext))
+ obj_names.append(os.path.join(output_dir, base + ext))
elif ext == '.rc':
# these need to be compiled to .res-files
- obj_names.append (os.path.join (output_dir, base + '.res'))
+ obj_names.append(os.path.join(output_dir, base + '.res'))
else:
- obj_names.append (os.path.join (output_dir,
- base + self.obj_extension))
+ obj_names.append(os.path.join(output_dir,
+ base + self.obj_extension))
return obj_names
- # object_filenames ()
- def preprocess (self,
- source,
- output_file=None,
- macros=None,
- include_dirs=None,
- extra_preargs=None,
- extra_postargs=None):
-
- (_, macros, include_dirs) = \
+ def preprocess(self, source, output_file=None, macros=None,
+ include_dirs=None, extra_preargs=None,
+ extra_postargs=None):
+ _, macros, include_dirs = \
self._fix_compile_args(None, macros, include_dirs)
pp_opts = gen_preprocess_options(macros, include_dirs)
pp_args = ['cpp32.exe'] + pp_opts
@@ -387,8 +351,7 @@
self.mkpath(os.path.dirname(output_file))
try:
self.spawn(pp_args)
- except DistutilsExecError, msg:
- print msg
- raise CompileError, msg
-
- # preprocess()
+ except PackagingExecError:
+ msg = sys.exc_info()[1]
+ print(msg)
+ raise CompileError(msg)
diff --git a/distutils2/compiler/ccompiler.py b/distutils2/compiler/ccompiler.py
--- a/distutils2/compiler/ccompiler.py
+++ b/distutils2/compiler/ccompiler.py
@@ -1,18 +1,15 @@
-"""distutils.ccompiler
+"""Abstract base class for compilers.
-Contains CCompiler, an abstract base class that defines the interface
-for the Distutils compiler abstraction model."""
+This modules contains CCompiler, an abstract base class that defines the
+interface for the compiler abstraction model used by distutils2.
+"""
-
+import os
import sys
-import os
-import re
from shutil import move
-
-from distutils2.errors import (CompileError, LinkError, UnknownFileError,
- DistutilsPlatformError, DistutilsModuleError)
+from distutils2 import logger
from distutils2.util import split_quoted, execute, newer_group, spawn
-from distutils2 import logger
+from distutils2.errors import (CompileError, LinkError, UnknownFileError)
from distutils2.compiler import gen_preprocess_options
@@ -75,15 +72,15 @@
# what language to use when mixing source types. For example, if some
# extension has two files with ".c" extension, and one with ".cpp", it
# is still linked as c++.
- language_map = {".c" : "c",
- ".cc" : "c++",
- ".cpp" : "c++",
- ".cxx" : "c++",
- ".m" : "objc",
+ language_map = {".c": "c",
+ ".cc": "c++",
+ ".cpp": "c++",
+ ".cxx": "c++",
+ ".m": "objc",
}
language_order = ["c++", "objc", "c"]
- def __init__ (self, verbose=0, dry_run=0, force=0):
+ def __init__(self, verbose=0, dry_run=False, force=False):
self.dry_run = dry_run
self.force = force
self.verbose = verbose
@@ -116,7 +113,7 @@
# named library files) to include on any link
self.objects = []
- for key, value in self.executables.iteritems():
+ for key, value in self.executables.items():
self.set_executable(key, value)
def set_executables(self, **args):
@@ -145,15 +142,14 @@
# discovered at run-time, since there are many different ways to do
# basically the same things with Unix C compilers.
- for key, value in args.iteritems():
+ for key, value in args.items():
if key not in self.executables:
- raise ValueError, \
- "unknown executable '%s' for class %s" % \
- (key, self.__class__.__name__)
+ raise ValueError("unknown executable '%s' for class %s" % \
+ (key, self.__class__.__name__))
self.set_executable(key, value)
def set_executable(self, key, value):
- if isinstance(value, str):
+ if isinstance(value, basestring):
setattr(self, key, split_quoted(value))
else:
setattr(self, key, value)
@@ -173,14 +169,13 @@
"""
for defn in definitions:
if not (isinstance(defn, tuple) and
- (len (defn) == 1 or
- (len (defn) == 2 and
- (isinstance(defn[1], str) or defn[1] is None))) and
- isinstance(defn[0], str)):
- raise TypeError, \
- ("invalid macro definition '%s': " % defn) + \
+ (len(defn) == 1 or
+ (len(defn) == 2 and
+ (isinstance(defn[1], basestring) or defn[1] is None))) and
+ isinstance(defn[0], basestring)):
+ raise TypeError(("invalid macro definition '%s': " % defn) + \
"must be tuple (string,), (string, string), or " + \
- "(string, None)"
+ "(string, None)")
# -- Bookkeeping methods -------------------------------------------
@@ -194,12 +189,12 @@
"""
# Delete from the list of macro definitions/undefinitions if
# already there (so that this one will take precedence).
- i = self._find_macro (name)
+ i = self._find_macro(name)
if i is not None:
del self.macros[i]
defn = (name, value)
- self.macros.append (defn)
+ self.macros.append(defn)
def undefine_macro(self, name):
"""Undefine a preprocessor macro for all compilations driven by
@@ -212,12 +207,12 @@
"""
# Delete from the list of macro definitions/undefinitions if
# already there (so that this one will take precedence).
- i = self._find_macro (name)
+ i = self._find_macro(name)
if i is not None:
del self.macros[i]
undefn = (name,)
- self.macros.append (undefn)
+ self.macros.append(undefn)
def add_include_dir(self, dir):
"""Add 'dir' to the list of directories that will be searched for
@@ -225,7 +220,7 @@
the order in which they are supplied by successive calls to
'add_include_dir()'.
"""
- self.include_dirs.append (dir)
+ self.include_dirs.append(dir)
def set_include_dirs(self, dirs):
"""Set the list of directories that will be searched to 'dirs' (a
@@ -251,7 +246,7 @@
names; the linker will be instructed to link against libraries as
many times as they are mentioned.
"""
- self.libraries.append (libname)
+ self.libraries.append(libname)
def set_libraries(self, libnames):
"""Set the list of libraries to be included in all links driven by
@@ -312,36 +307,35 @@
# (here for the convenience of subclasses)
# Helper method to prep compiler in subclass compile() methods
-
def _setup_compile(self, outdir, macros, incdirs, sources, depends,
extra):
"""Process arguments and decide which source files to compile."""
if outdir is None:
outdir = self.output_dir
- elif not isinstance(outdir, str):
- raise TypeError, "'output_dir' must be a string or None"
+ elif not isinstance(outdir, basestring):
+ raise TypeError("'output_dir' must be a string or None")
if macros is None:
macros = self.macros
elif isinstance(macros, list):
macros = macros + (self.macros or [])
else:
- raise TypeError, "'macros' (if supplied) must be a list of tuples"
+ raise TypeError("'macros' (if supplied) must be a list of tuples")
if incdirs is None:
incdirs = self.include_dirs
elif isinstance(incdirs, (list, tuple)):
incdirs = list(incdirs) + (self.include_dirs or [])
else:
- raise TypeError, \
- "'include_dirs' (if supplied) must be a list of strings"
+ raise TypeError(
+ "'include_dirs' (if supplied) must be a list of strings")
if extra is None:
extra = []
# Get the list of expected output (object) files
objects = self.object_filenames(sources,
- strip_dir=0,
+ strip_dir=False,
output_dir=outdir)
assert len(objects) == len(sources)
@@ -358,7 +352,7 @@
return macros, objects, extra, pp_opts, build
def _get_cc_args(self, pp_opts, debug, before):
- # works for unixccompiler, emxccompiler, cygwinccompiler
+ # works for unixccompiler and cygwinccompiler
cc_args = pp_opts + ['-c']
if debug:
cc_args[:0] = ['-g']
@@ -378,23 +372,23 @@
"""
if output_dir is None:
output_dir = self.output_dir
- elif not isinstance(output_dir, str):
- raise TypeError, "'output_dir' must be a string or None"
+ elif not isinstance(output_dir, basestring):
+ raise TypeError("'output_dir' must be a string or None")
if macros is None:
macros = self.macros
elif isinstance(macros, list):
macros = macros + (self.macros or [])
else:
- raise TypeError, "'macros' (if supplied) must be a list of tuples"
+ raise TypeError("'macros' (if supplied) must be a list of tuples")
if include_dirs is None:
include_dirs = self.include_dirs
elif isinstance(include_dirs, (list, tuple)):
- include_dirs = list (include_dirs) + (self.include_dirs or [])
+ include_dirs = list(include_dirs) + (self.include_dirs or [])
else:
- raise TypeError, \
- "'include_dirs' (if supplied) must be a list of strings"
+ raise TypeError(
+ "'include_dirs' (if supplied) must be a list of strings")
return output_dir, macros, include_dirs
@@ -405,16 +399,15 @@
'objects' and 'output_dir'.
"""
if not isinstance(objects, (list, tuple)):
- raise TypeError, \
- "'objects' must be a list or tuple of strings"
- objects = list (objects)
+ raise TypeError("'objects' must be a list or tuple of strings")
+ objects = list(objects)
if output_dir is None:
output_dir = self.output_dir
- elif not isinstance(output_dir, str):
- raise TypeError, "'output_dir' must be a string or None"
+ elif not isinstance(output_dir, basestring):
+ raise TypeError("'output_dir' must be a string or None")
- return (objects, output_dir)
+ return objects, output_dir
def _fix_lib_args(self, libraries, library_dirs, runtime_library_dirs):
"""Typecheck and fix up some of the arguments supplied to the
@@ -426,37 +419,36 @@
if libraries is None:
libraries = self.libraries
elif isinstance(libraries, (list, tuple)):
- libraries = list (libraries) + (self.libraries or [])
+ libraries = list(libraries) + (self.libraries or [])
else:
- raise TypeError, \
- "'libraries' (if supplied) must be a list of strings"
+ raise TypeError(
+ "'libraries' (if supplied) must be a list of strings")
if library_dirs is None:
library_dirs = self.library_dirs
elif isinstance(library_dirs, (list, tuple)):
- library_dirs = list (library_dirs) + (self.library_dirs or [])
+ library_dirs = list(library_dirs) + (self.library_dirs or [])
else:
- raise TypeError, \
- "'library_dirs' (if supplied) must be a list of strings"
+ raise TypeError(
+ "'library_dirs' (if supplied) must be a list of strings")
if runtime_library_dirs is None:
runtime_library_dirs = self.runtime_library_dirs
elif isinstance(runtime_library_dirs, (list, tuple)):
- runtime_library_dirs = (list (runtime_library_dirs) +
+ runtime_library_dirs = (list(runtime_library_dirs) +
(self.runtime_library_dirs or []))
else:
- raise TypeError, \
- "'runtime_library_dirs' (if supplied) " + \
- "must be a list of strings"
+ raise TypeError("'runtime_library_dirs' (if supplied) "
+ "must be a list of strings")
- return (libraries, library_dirs, runtime_library_dirs)
+ return libraries, library_dirs, runtime_library_dirs
def _need_link(self, objects, output_file):
"""Return true if we need to relink the files listed in 'objects'
to recreate 'output_file'.
"""
if self.force:
- return 1
+ return True
else:
if self.dry_run:
newer = newer_group(objects, output_file, missing='newer')
@@ -501,7 +493,7 @@
pass
def compile(self, sources, output_dir=None, macros=None,
- include_dirs=None, debug=0, extra_preargs=None,
+ include_dirs=None, debug=False, extra_preargs=None,
extra_postargs=None, depends=None):
"""Compile one or more source files.
@@ -577,7 +569,7 @@
pass
def create_static_lib(self, objects, output_libname, output_dir=None,
- debug=0, target_lang=None):
+ debug=False, target_lang=None):
"""Link a bunch of stuff together to create a static library file.
The "bunch of stuff" consists of the list of object files supplied
as 'objects', the extra object files supplied to
@@ -609,7 +601,7 @@
def link(self, target_desc, objects, output_filename, output_dir=None,
libraries=None, library_dirs=None, runtime_library_dirs=None,
- export_symbols=None, debug=0, extra_preargs=None,
+ export_symbols=None, debug=False, extra_preargs=None,
extra_postargs=None, build_temp=None, target_lang=None):
"""Link a bunch of stuff together to create an executable or
shared library file.
@@ -662,7 +654,7 @@
def link_shared_lib(self, objects, output_libname, output_dir=None,
libraries=None, library_dirs=None,
runtime_library_dirs=None, export_symbols=None,
- debug=0, extra_preargs=None, extra_postargs=None,
+ debug=False, extra_preargs=None, extra_postargs=None,
build_temp=None, target_lang=None):
self.link(CCompiler.SHARED_LIBRARY, objects,
self.library_filename(output_libname, lib_type='shared'),
@@ -671,11 +663,10 @@
export_symbols, debug,
extra_preargs, extra_postargs, build_temp, target_lang)
-
def link_shared_object(self, objects, output_filename, output_dir=None,
libraries=None, library_dirs=None,
runtime_library_dirs=None, export_symbols=None,
- debug=0, extra_preargs=None, extra_postargs=None,
+ debug=False, extra_preargs=None, extra_postargs=None,
build_temp=None, target_lang=None):
self.link(CCompiler.SHARED_OBJECT, objects,
output_filename, output_dir,
@@ -685,8 +676,9 @@
def link_executable(self, objects, output_progname, output_dir=None,
libraries=None, library_dirs=None,
- runtime_library_dirs=None, debug=0, extra_preargs=None,
- extra_postargs=None, target_lang=None):
+ runtime_library_dirs=None, debug=False,
+ extra_preargs=None, extra_postargs=None,
+ target_lang=None):
self.link(CCompiler.EXECUTABLE, objects,
self.executable_filename(output_progname), output_dir,
libraries, library_dirs, runtime_library_dirs, None,
@@ -736,8 +728,7 @@
if library_dirs is None:
library_dirs = []
fd, fname = tempfile.mkstemp(".c", funcname, text=True)
- f = os.fdopen(fd, "w")
- try:
+ with os.fdopen(fd, "w") as f:
for incl in includes:
f.write("""#include "%s"\n""" % incl)
f.write("""\
@@ -745,8 +736,6 @@
%s();
}
""" % funcname)
- finally:
- f.close()
try:
objects = self.compile([fname], include_dirs=include_dirs)
except CompileError:
@@ -760,10 +749,10 @@
return False
return True
- def find_library_file (self, dirs, lib, debug=0):
+ def find_library_file(self, dirs, lib, debug=False):
"""Search the specified list of directories for a static or shared
library file 'lib' and return the full path to that file. If
- 'debug' true, look for a debugging version (if that makes sense on
+ 'debug' is true, look for a debugging version (if that makes sense on
the current platform). Return None if 'lib' wasn't found in any of
the specified directories.
"""
@@ -803,44 +792,45 @@
# * exe_extension -
# extension for executable files, eg. '' or '.exe'
- def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):
+ def object_filenames(self, source_filenames, strip_dir=False, output_dir=''):
if output_dir is None:
output_dir = ''
obj_names = []
for src_name in source_filenames:
base, ext = os.path.splitext(src_name)
- base = os.path.splitdrive(base)[1] # Chop off the drive
+ base = os.path.splitdrive(base)[1] # Chop off the drive
base = base[os.path.isabs(base):] # If abs, chop off leading /
if ext not in self.src_extensions:
- raise UnknownFileError, \
- "unknown file type '%s' (from '%s')" % (ext, src_name)
+ raise UnknownFileError("unknown file type '%s' (from '%s')" %
+ (ext, src_name))
if strip_dir:
base = os.path.basename(base)
obj_names.append(os.path.join(output_dir,
base + self.obj_extension))
return obj_names
- def shared_object_filename(self, basename, strip_dir=0, output_dir=''):
+ def shared_object_filename(self, basename, strip_dir=False, output_dir=''):
assert output_dir is not None
if strip_dir:
- basename = os.path.basename (basename)
+ basename = os.path.basename(basename)
return os.path.join(output_dir, basename + self.shared_lib_extension)
- def executable_filename(self, basename, strip_dir=0, output_dir=''):
+ def executable_filename(self, basename, strip_dir=False, output_dir=''):
assert output_dir is not None
if strip_dir:
- basename = os.path.basename (basename)
+ basename = os.path.basename(basename)
return os.path.join(output_dir, basename + (self.exe_extension or ''))
def library_filename(self, libname, lib_type='static', # or 'shared'
- strip_dir=0, output_dir=''):
+ strip_dir=False, output_dir=''):
assert output_dir is not None
if lib_type not in ("static", "shared", "dylib"):
- raise ValueError, "'lib_type' must be \"static\", \"shared\" or \"dylib\""
+ raise ValueError(
+ "'lib_type' must be 'static', 'shared' or 'dylib'")
fmt = getattr(self, lib_type + "_lib_format")
ext = getattr(self, lib_type + "_lib_extension")
- dir, base = os.path.split (libname)
+ dir, base = os.path.split(libname)
filename = fmt % (base, ext)
if strip_dir:
dir = ''
@@ -850,19 +840,6 @@
# -- Utility methods -----------------------------------------------
- # TODO use logging.info
- def announce(self, msg, level=None):
- logger.debug(msg)
-
- def debug_print(self, msg):
- from distutils2.debug import DEBUG
- if DEBUG:
- print msg
-
- # TODO use logging.warn
- def warn(self, msg):
- sys.stderr.write("warning: %s\n" % msg)
-
def execute(self, func, args, msg=None, level=1):
execute(func, args, msg, self.dry_run)
@@ -870,11 +847,12 @@
spawn(cmd, dry_run=self.dry_run)
def move_file(self, src, dst):
+ logger.info("moving %r to %r", src, dst)
if self.dry_run:
- return # XXX log ?
+ return
return move(src, dst)
- def mkpath(self, name, mode=0777):
+ def mkpath(self, name, mode=0o777):
name = os.path.normpath(name)
if os.path.isdir(name) or name == '':
return
diff --git a/distutils2/compiler/cygwinccompiler.py b/distutils2/compiler/cygwinccompiler.py
--- a/distutils2/compiler/cygwinccompiler.py
+++ b/distutils2/compiler/cygwinccompiler.py
@@ -1,9 +1,9 @@
-"""distutils.cygwinccompiler
+"""CCompiler implementations for Cygwin and mingw32 versions of GCC.
-Provides the CygwinCCompiler class, a subclass of UnixCCompiler that
-handles the Cygwin port of the GNU C compiler to Windows. It also contains
-the Mingw32CCompiler class which handles the mingw32 port of GCC (same as
-cygwin in no-cygwin mode).
+This module contains the CygwinCCompiler class, a subclass of
+UnixCCompiler that handles the Cygwin port of the GNU C compiler to
+Windows, and the Mingw32CCompiler class which handles the mingw32 port
+of GCC (same as cygwin in no-cygwin mode).
"""
# problems:
@@ -48,15 +48,13 @@
import os
import sys
-import copy
-import re
-from warnings import warn
+from distutils2 import logger
from distutils2.compiler.unixccompiler import UnixCCompiler
from distutils2.util import write_file
-from distutils2.errors import DistutilsExecError, CompileError, UnknownFileError
+from distutils2.errors import PackagingExecError, CompileError, UnknownFileError
from distutils2.util import get_compiler_versions
-from distutils2._backport import sysconfig
+import sysconfig
def get_msvcr():
@@ -94,13 +92,12 @@
shared_lib_format = "%s%s"
exe_extension = ".exe"
- def __init__(self, verbose=0, dry_run=0, force=0):
+ def __init__(self, verbose=0, dry_run=False, force=False):
UnixCCompiler.__init__(self, verbose, dry_run, force)
status, details = check_config_h()
- self.debug_print("Python's GCC status: %s (details: %s)" %
- (status, details))
+ logger.debug("Python's GCC status: %s (details: %s)", status, details)
if status is not CONFIG_H_OK:
self.warn(
"Python's pyconfig.h doesn't seem to support your compiler. "
@@ -110,10 +107,10 @@
self.gcc_version, self.ld_version, self.dllwrap_version = \
get_compiler_versions()
- self.debug_print(self.name + ": gcc %s, ld %s, dllwrap %s\n" %
- (self.gcc_version,
- self.ld_version,
- self.dllwrap_version) )
+ logger.debug(self.name + ": gcc %s, ld %s, dllwrap %s\n",
+ self.gcc_version,
+ self.ld_version,
+ self.dllwrap_version)
# ld_version >= "2.10.90" and < "2.13" should also be able to use
# gcc -mdll instead of dllwrap
@@ -154,29 +151,29 @@
self.dll_libraries = get_msvcr()
def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
- """Compiles the source by spawing GCC and windres if needed."""
+ """Compile the source by spawning GCC and windres if needed."""
if ext == '.rc' or ext == '.res':
# gcc needs '.res' and '.rc' compiled to object files !!!
try:
self.spawn(["windres", "-i", src, "-o", obj])
- except DistutilsExecError, msg:
- raise CompileError, msg
+ except PackagingExecError:
+ raise CompileError(sys.exc_info()[1])
else: # for other files use the C-compiler
try:
self.spawn(self.compiler_so + cc_args + [src, '-o', obj] +
extra_postargs)
- except DistutilsExecError, msg:
- raise CompileError, msg
+ except PackagingExecError:
+ raise CompileError(sys.exc_info()[1])
def link(self, target_desc, objects, output_filename, output_dir=None,
libraries=None, library_dirs=None, runtime_library_dirs=None,
- export_symbols=None, debug=0, extra_preargs=None,
+ export_symbols=None, debug=False, extra_preargs=None,
extra_postargs=None, build_temp=None, target_lang=None):
"""Link the objects."""
# use separate copies, so we can modify the lists
- extra_preargs = copy.copy(extra_preargs or [])
- libraries = copy.copy(libraries or [])
- objects = copy.copy(objects or [])
+ extra_preargs = list(extra_preargs or [])
+ libraries = list(libraries or [])
+ objects = list(objects or [])
# Additional libraries
libraries.extend(self.dll_libraries)
@@ -195,7 +192,7 @@
# where are the object files
temp_dir = os.path.dirname(objects[0])
# name of dll to give the helper files the same base name
- (dll_name, dll_extension) = os.path.splitext(
+ dll_name, dll_extension = os.path.splitext(
os.path.basename(output_filename))
# generate the filenames for these files
@@ -215,13 +212,13 @@
# dllwrap uses different options than gcc/ld
if self.linker_dll == "dllwrap":
- extra_preargs.extend(["--output-lib", lib_file])
+ extra_preargs.extend(("--output-lib", lib_file))
# for dllwrap we have to use a special option
- extra_preargs.extend(["--def", def_file])
+ extra_preargs.extend(("--def", def_file))
# we use gcc/ld here and can be sure ld is >= 2.9.10
else:
# doesn't work: bfd_close build\...\libfoo.a: Invalid operation
- #extra_preargs.extend(["-Wl,--out-implib,%s" % lib_file])
+ #extra_preargs.extend(("-Wl,--out-implib,%s" % lib_file))
# for gcc/ld the def-file is specified as any object files
objects.append(def_file)
@@ -246,7 +243,8 @@
# -- Miscellaneous methods -----------------------------------------
- def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):
+ def object_filenames(self, source_filenames, strip_dir=False,
+ output_dir=''):
"""Adds supports for rc and res files."""
if output_dir is None:
output_dir = ''
@@ -255,8 +253,7 @@
# use normcase to make sure '.rc' is really '.rc' and not '.RC'
base, ext = os.path.splitext(os.path.normcase(src_name))
if ext not in (self.src_extensions + ['.rc','.res']):
- raise UnknownFileError, \
- "unknown file type '%s' (from '%s')" % (ext, src_name)
+ raise UnknownFileError("unknown file type '%s' (from '%s')" % (ext, src_name))
if strip_dir:
base = os.path.basename (base)
if ext in ('.res', '.rc'):
@@ -275,7 +272,7 @@
name = 'mingw32'
description = 'MinGW32 compiler'
- def __init__(self, verbose=0, dry_run=0, force=0):
+ def __init__(self, verbose=0, dry_run=False, force=False):
CygwinCCompiler.__init__ (self, verbose, dry_run, force)
@@ -347,14 +344,12 @@
# let's see if __GNUC__ is mentioned in python.h
fn = sysconfig.get_config_h_filename()
try:
- config_h = open(fn)
- try:
+ with open(fn) as config_h:
if "__GNUC__" in config_h.read():
return CONFIG_H_OK, "'%s' mentions '__GNUC__'" % fn
else:
return CONFIG_H_NOTOK, "'%s' does not mention '__GNUC__'" % fn
- finally:
- config_h.close()
- except IOError, exc:
+ except IOError:
+ exc = sys.exc_info()[1]
return (CONFIG_H_UNCERTAIN,
"couldn't read '%s': %s" % (fn, exc.strerror))
diff --git a/distutils2/compiler/extension.py b/distutils2/compiler/extension.py
--- a/distutils2/compiler/extension.py
+++ b/distutils2/compiler/extension.py
@@ -1,10 +1,6 @@
-"""distutils.extension
+"""Class representing C/C++ extension modules."""
-Provides the Extension class, used to describe C/C++ extension
-modules in setup scripts."""
-
-
-import warnings
+from distutils2 import logger
# This class is really only used by the "build_ext" command, so it might
# make sense to put it in distutils.command.build_ext. However, that
@@ -82,33 +78,22 @@
build process, but simply not install the failing extension.
"""
- # When adding arguments to this constructor, be sure to update
- # setup_keywords in core.py.
- def __init__(self, name, sources,
- include_dirs=None,
- define_macros=None,
- undef_macros=None,
- library_dirs=None,
- libraries=None,
- runtime_library_dirs=None,
- extra_objects=None,
- extra_compile_args=None,
- extra_link_args=None,
- export_symbols=None,
- swig_opts=None,
- depends=None,
- language=None,
- optional=None,
- **kw # To catch unknown keywords
- ):
- if not isinstance(name, str):
+ # **kwargs are allowed so that a warning is emitted instead of an
+ # exception
+ def __init__(self, name, sources, include_dirs=None, define_macros=None,
+ undef_macros=None, library_dirs=None, libraries=None,
+ runtime_library_dirs=None, extra_objects=None,
+ extra_compile_args=None, extra_link_args=None,
+ export_symbols=None, swig_opts=None, depends=None,
+ language=None, optional=None, **kw):
+ if not isinstance(name, basestring):
raise AssertionError("'name' must be a string")
if not isinstance(sources, list):
raise AssertionError("'sources' must be a list of strings")
for v in sources:
- if not isinstance(v, str):
+ if not isinstance(v, basestring):
raise AssertionError("'sources' must be a list of strings")
self.name = name
@@ -132,5 +117,5 @@
if len(kw) > 0:
options = [repr(option) for option in kw]
options = ', '.join(sorted(options))
- msg = "Unknown Extension options: %s" % options
- warnings.warn(msg)
+ logger.warning(
+ 'unknown arguments given to Extension: %s', options)
diff --git a/distutils2/compiler/msvc9compiler.py b/distutils2/compiler/msvc9compiler.py
--- a/distutils2/compiler/msvc9compiler.py
+++ b/distutils2/compiler/msvc9compiler.py
@@ -1,10 +1,7 @@
-"""distutils.msvc9compiler
+"""CCompiler implementation for the Microsoft Visual Studio 2008 compiler.
-Contains MSVCCompiler, an implementation of the abstract CCompiler class
-for the Microsoft Visual Studio 2008.
-
-The module is compatible with VS 2005 and VS 2008. You can find legacy support
-for older versions of VS in distutils.msvccompiler.
+The MSVCCompiler class is compatible with VS 2005 and VS 2008. Legacy
+support for older versions of VS are in the msvccompiler module.
"""
# Written by Perry Stoll
@@ -16,24 +13,24 @@
import sys
import re
-from distutils2.errors import (DistutilsExecError, DistutilsPlatformError,
- CompileError, LibError, LinkError)
+from distutils2.errors import (PackagingExecError, PackagingPlatformError,
+ CompileError, LibError, LinkError)
from distutils2.compiler.ccompiler import CCompiler
from distutils2.compiler import gen_lib_options
from distutils2 import logger
from distutils2.util import get_platform
-import _winreg
+import winreg
-RegOpenKeyEx = _winreg.OpenKeyEx
-RegEnumKey = _winreg.EnumKey
-RegEnumValue = _winreg.EnumValue
-RegError = _winreg.error
+RegOpenKeyEx = winreg.OpenKeyEx
+RegEnumKey = winreg.EnumKey
+RegEnumValue = winreg.EnumValue
+RegError = winreg.error
-HKEYS = (_winreg.HKEY_USERS,
- _winreg.HKEY_CURRENT_USER,
- _winreg.HKEY_LOCAL_MACHINE,
- _winreg.HKEY_CLASSES_ROOT)
+HKEYS = (winreg.HKEY_USERS,
+ winreg.HKEY_CURRENT_USER,
+ winreg.HKEY_LOCAL_MACHINE,
+ winreg.HKEY_CLASSES_ROOT)
VS_BASE = r"Software\Microsoft\VisualStudio\%0.1f"
WINSDK_BASE = r"Software\Microsoft\Microsoft SDKs\Windows"
@@ -132,11 +129,11 @@
else:
raise KeyError("sdkinstallrootv2.0")
except KeyError:
- raise DistutilsPlatformError(
- """Python was built with Visual Studio 2008;
-extensions must be built with a compiler than can generate compatible binaries.
-Visual Studio 2008 was not found on this system. If you have Cygwin installed,
-you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""")
+ raise PackagingPlatformError(
+"""Python was built with Visual Studio 2008; extensions must be built with a
+compiler than can generate compatible binaries. Visual Studio 2008 was not
+found on this system. If you have Cygwin installed, you can try compiling
+with MingW32, by passing "-c mingw32" to pysetup.""")
if version >= 9.0:
self.set_macro("FrameworkVersion", self.vsbase, "clr version")
@@ -153,7 +150,7 @@
self.macros["$(FrameworkVersion)"] = d["version"]
def sub(self, s):
- for k, v in self.macros.iteritems():
+ for k, v in self.macros.items():
s = s.replace(k, v)
return s
@@ -247,7 +244,7 @@
result = {}
if vcvarsall is None:
- raise DistutilsPlatformError("Unable to find vcvarsall.bat")
+ raise PackagingPlatformError("Unable to find vcvarsall.bat")
logger.debug("calling 'vcvarsall.bat %s' (version=%s)", arch, version)
popen = subprocess.Popen('"%s" %s & set' % (vcvarsall, arch),
stdout=subprocess.PIPE,
@@ -255,7 +252,7 @@
stdout, stderr = popen.communicate()
if popen.wait() != 0:
- raise DistutilsPlatformError(stderr.decode("mbcs"))
+ raise PackagingPlatformError(stderr.decode("mbcs"))
stdout = stdout.decode("mbcs")
for line in stdout.split("\n"):
@@ -278,7 +275,7 @@
# More globals
VERSION = get_build_version()
if VERSION < 8.0:
- raise DistutilsPlatformError("VC %0.1f is not supported by this module" % VERSION)
+ raise PackagingPlatformError("VC %0.1f is not supported by this module" % VERSION)
# MACROS = MacroExpander(VERSION)
class MSVCCompiler(CCompiler) :
@@ -312,8 +309,8 @@
static_lib_format = shared_lib_format = '%s%s'
exe_extension = '.exe'
- def __init__(self, verbose=0, dry_run=0, force=0):
- CCompiler.__init__ (self, verbose, dry_run, force)
+ def __init__(self, verbose=0, dry_run=False, force=False):
+ CCompiler.__init__(self, verbose, dry_run, force)
self.__version = VERSION
self.__root = r"Software\Microsoft\VisualStudio"
# self.__macros = MACROS
@@ -331,7 +328,7 @@
# sanity check for platforms to prevent obscure errors later.
ok_plats = 'win32', 'win-amd64', 'win-ia64'
if plat_name not in ok_plats:
- raise DistutilsPlatformError("--plat-name must be one of %s" %
+ raise PackagingPlatformError("--plat-name must be one of %s" %
(ok_plats,))
if "DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and self.find_exe("cl.exe"):
@@ -359,12 +356,12 @@
vc_env = query_vcvarsall(VERSION, plat_spec)
# take care to only use strings in the environment.
- self.__paths = vc_env['path'].encode('mbcs').split(os.pathsep)
- os.environ['lib'] = vc_env['lib'].encode('mbcs')
- os.environ['include'] = vc_env['include'].encode('mbcs')
+ self.__paths = vc_env['path'].split(os.pathsep)
+ os.environ['lib'] = vc_env['lib']
+ os.environ['include'] = vc_env['include']
if len(self.__paths) == 0:
- raise DistutilsPlatformError("Python was built with %s, "
+ raise PackagingPlatformError("Python was built with %s, "
"and extensions need to be built with the same "
"version of the compiler, but it isn't installed."
% self.__product)
@@ -412,37 +409,37 @@
def object_filenames(self,
source_filenames,
- strip_dir=0,
+ strip_dir=False,
output_dir=''):
# Copied from ccompiler.py, extended to return .res as 'object'-file
# for .rc input file
if output_dir is None: output_dir = ''
obj_names = []
for src_name in source_filenames:
- (base, ext) = os.path.splitext (src_name)
+ base, ext = os.path.splitext(src_name)
base = os.path.splitdrive(base)[1] # Chop off the drive
base = base[os.path.isabs(base):] # If abs, chop off leading /
if ext not in self.src_extensions:
# Better to raise an exception instead of silently continuing
# and later complain about sources and targets having
# different lengths
- raise CompileError ("Don't know how to compile %s" % src_name)
+ raise CompileError("Don't know how to compile %s" % src_name)
if strip_dir:
- base = os.path.basename (base)
+ base = os.path.basename(base)
if ext in self._rc_extensions:
- obj_names.append (os.path.join (output_dir,
- base + self.res_extension))
+ obj_names.append(os.path.join(output_dir,
+ base + self.res_extension))
elif ext in self._mc_extensions:
- obj_names.append (os.path.join (output_dir,
- base + self.res_extension))
+ obj_names.append(os.path.join(output_dir,
+ base + self.res_extension))
else:
- obj_names.append (os.path.join (output_dir,
- base + self.obj_extension))
+ obj_names.append(os.path.join(output_dir,
+ base + self.obj_extension))
return obj_names
def compile(self, sources,
- output_dir=None, macros=None, include_dirs=None, debug=0,
+ output_dir=None, macros=None, include_dirs=None, debug=False,
extra_preargs=None, extra_postargs=None, depends=None):
if not self.initialized:
@@ -452,7 +449,7 @@
macros, objects, extra_postargs, pp_opts, build = compile_info
compile_opts = extra_preargs or []
- compile_opts.append ('/c')
+ compile_opts.append('/c')
if debug:
compile_opts.extend(self.compile_options_debug)
else:
@@ -480,8 +477,8 @@
try:
self.spawn([self.rc] + pp_opts +
[output_opt] + [input_opt])
- except DistutilsExecError, msg:
- raise CompileError(msg)
+ except PackagingExecError:
+ raise CompileError(sys.exc_info()[1])
continue
elif ext in self._mc_extensions:
# Compile .MC to .RC file to .RES file.
@@ -501,14 +498,14 @@
# first compile .MC to .RC and .H file
self.spawn([self.mc] +
['-h', h_dir, '-r', rc_dir] + [src])
- base, _ = os.path.splitext (os.path.basename (src))
- rc_file = os.path.join (rc_dir, base + '.rc')
+ base, _ = os.path.splitext(os.path.basename(src))
+ rc_file = os.path.join(rc_dir, base + '.rc')
# then compile .RC to .RES file
self.spawn([self.rc] +
["/fo" + obj] + [rc_file])
- except DistutilsExecError, msg:
- raise CompileError(msg)
+ except PackagingExecError:
+ raise CompileError(sys.exc_info()[1])
continue
else:
# how to handle this file?
@@ -520,8 +517,8 @@
self.spawn([self.cc] + compile_opts + pp_opts +
[input_opt, output_opt] +
extra_postargs)
- except DistutilsExecError, msg:
- raise CompileError(msg)
+ except PackagingExecError:
+ raise CompileError(sys.exc_info()[1])
return objects
@@ -530,12 +527,12 @@
objects,
output_libname,
output_dir=None,
- debug=0,
+ debug=False,
target_lang=None):
if not self.initialized:
self.initialize()
- (objects, output_dir) = self._fix_object_args(objects, output_dir)
+ objects, output_dir = self._fix_object_args(objects, output_dir)
output_filename = self.library_filename(output_libname,
output_dir=output_dir)
@@ -545,37 +542,26 @@
pass # XXX what goes here?
try:
self.spawn([self.lib] + lib_args)
- except DistutilsExecError, msg:
- raise LibError(msg)
+ except PackagingExecError:
+ raise LibError(sys.exc_info()[1])
else:
logger.debug("skipping %s (up-to-date)", output_filename)
- def link(self,
- target_desc,
- objects,
- output_filename,
- output_dir=None,
- libraries=None,
- library_dirs=None,
- runtime_library_dirs=None,
- export_symbols=None,
- debug=0,
- extra_preargs=None,
- extra_postargs=None,
- build_temp=None,
- target_lang=None):
-
+ def link(self, target_desc, objects, output_filename, output_dir=None,
+ libraries=None, library_dirs=None, runtime_library_dirs=None,
+ export_symbols=None, debug=False, extra_preargs=None,
+ extra_postargs=None, build_temp=None, target_lang=None):
if not self.initialized:
self.initialize()
- (objects, output_dir) = self._fix_object_args(objects, output_dir)
+ objects, output_dir = self._fix_object_args(objects, output_dir)
fixed_args = self._fix_lib_args(libraries, library_dirs,
runtime_library_dirs)
- (libraries, library_dirs, runtime_library_dirs) = fixed_args
+ libraries, library_dirs, runtime_library_dirs = fixed_args
if runtime_library_dirs:
- self.warn ("I don't know what to do with 'runtime_library_dirs': "
- + str (runtime_library_dirs))
+ self.warn("don't know what to do with 'runtime_library_dirs': "
+ + str(runtime_library_dirs))
lib_opts = gen_lib_options(self,
library_dirs, runtime_library_dirs,
@@ -609,12 +595,12 @@
# builds, they can go into the same directory.
build_temp = os.path.dirname(objects[0])
if export_symbols is not None:
- (dll_name, dll_ext) = os.path.splitext(
+ dll_name, dll_ext = os.path.splitext(
os.path.basename(output_filename))
implib_file = os.path.join(
build_temp,
self.library_filename(dll_name))
- ld_args.append ('/IMPLIB:' + implib_file)
+ ld_args.append('/IMPLIB:' + implib_file)
# Embedded manifests are recommended - see MSDN article titled
# "How to: Embed a Manifest Inside a C/C++ Application"
@@ -634,8 +620,8 @@
self.mkpath(os.path.dirname(output_filename))
try:
self.spawn([self.linker] + ld_args)
- except DistutilsExecError, msg:
- raise LinkError(msg)
+ except PackagingExecError:
+ raise LinkError(sys.exc_info()[1])
# embed the manifest
# XXX - this is somewhat fragile - if mt.exe fails, distutils
@@ -651,8 +637,8 @@
try:
self.spawn(['mt.exe', '-nologo', '-manifest',
temp_manifest, out_arg])
- except DistutilsExecError, msg:
- raise LinkError(msg)
+ except PackagingExecError:
+ raise LinkError(sys.exc_info()[1])
else:
logger.debug("skipping %s (up-to-date)", output_filename)
@@ -664,11 +650,8 @@
# runtimes are not in WinSxS folder, but in Python's own
# folder), the runtimes do not need to be in every folder
# with .pyd's.
- manifest_f = open(manifest_file)
- try:
+ with open(manifest_file) as manifest_f:
manifest_buf = manifest_f.read()
- finally:
- manifest_f.close()
pattern = re.compile(
r"""<assemblyIdentity.*?name=("|')Microsoft\."""\
r"""VC\d{2}\.CRT("|').*?(/>|</assemblyIdentity>)""",
@@ -676,11 +659,8 @@
manifest_buf = re.sub(pattern, "", manifest_buf)
pattern = "<dependentAssembly>\s*</dependentAssembly>"
manifest_buf = re.sub(pattern, "", manifest_buf)
- manifest_f = open(manifest_file, 'w')
- try:
+ with open(manifest_file, 'w') as manifest_f:
manifest_f.write(manifest_buf)
- finally:
- manifest_f.close()
except IOError:
pass
@@ -692,14 +672,14 @@
return "/LIBPATH:" + dir
def runtime_library_dir_option(self, dir):
- raise DistutilsPlatformError(
+ raise PackagingPlatformError(
"don't know how to set runtime library search path for MSVC++")
def library_option(self, lib):
return self.library_filename(lib)
- def find_library_file(self, dirs, lib, debug=0):
+ def find_library_file(self, dirs, lib, debug=False):
# Prefer a debugging library if found (and requested), but deal
# with it if we don't have one.
if debug:
@@ -708,7 +688,7 @@
try_names = [lib]
for dir in dirs:
for name in try_names:
- libfile = os.path.join(dir, self.library_filename (name))
+ libfile = os.path.join(dir, self.library_filename(name))
if os.path.exists(libfile):
return libfile
else:
diff --git a/distutils2/compiler/msvccompiler.py b/distutils2/compiler/msvccompiler.py
--- a/distutils2/compiler/msvccompiler.py
+++ b/distutils2/compiler/msvccompiler.py
@@ -1,7 +1,6 @@
-"""distutils.msvccompiler
+"""CCompiler implementation for old Microsoft Visual Studio compilers.
-Contains MSVCCompiler, an implementation of the abstract CCompiler class
-for the Microsoft Visual Studio.
+For a compiler compatible with VS 2005 and 2008, use msvc9compiler.
"""
# Written by Perry Stoll
@@ -11,31 +10,30 @@
import sys
import os
-import string
-from distutils2.errors import (DistutilsExecError, DistutilsPlatformError,
- CompileError, LibError, LinkError)
+from distutils2.errors import (PackagingExecError, PackagingPlatformError,
+ CompileError, LibError, LinkError)
from distutils2.compiler.ccompiler import CCompiler
from distutils2.compiler import gen_lib_options
from distutils2 import logger
-_can_read_reg = 0
+_can_read_reg = False
try:
- import _winreg
+ import winreg
- _can_read_reg = 1
- hkey_mod = _winreg
+ _can_read_reg = True
+ hkey_mod = winreg
- RegOpenKeyEx = _winreg.OpenKeyEx
- RegEnumKey = _winreg.EnumKey
- RegEnumValue = _winreg.EnumValue
- RegError = _winreg.error
+ RegOpenKeyEx = winreg.OpenKeyEx
+ RegEnumKey = winreg.EnumKey
+ RegEnumValue = winreg.EnumValue
+ RegError = winreg.error
except ImportError:
try:
import win32api
import win32con
- _can_read_reg = 1
+ _can_read_reg = True
hkey_mod = win32con
RegOpenKeyEx = win32api.RegOpenKeyEx
@@ -55,6 +53,7 @@
hkey_mod.HKEY_LOCAL_MACHINE,
hkey_mod.HKEY_CLASSES_ROOT)
+
def read_keys(base, key):
"""Return list of registry keys."""
@@ -64,7 +63,7 @@
return None
L = []
i = 0
- while 1:
+ while True:
try:
k = RegEnumKey(handle, i)
except RegError:
@@ -73,6 +72,7 @@
i = i + 1
return L
+
def read_values(base, key):
"""Return dict of registry keys and values.
@@ -84,7 +84,7 @@
return None
d = {}
i = 0
- while 1:
+ while True:
try:
name, value, type = RegEnumValue(handle, i)
except RegError:
@@ -94,6 +94,7 @@
i = i + 1
return d
+
def convert_mbcs(s):
enc = getattr(s, "encode", None)
if enc is not None:
@@ -103,6 +104,7 @@
pass
return s
+
class MacroExpander(object):
def __init__(self, version):
@@ -128,11 +130,11 @@
else:
self.set_macro("FrameworkSDKDir", net, "sdkinstallroot")
except KeyError:
- raise DistutilsPlatformError, \
- ("""Python was built with Visual Studio 2003;
-extensions must be built with a compiler than can generate compatible binaries.
-Visual Studio 2003 was not found on this system. If you have Cygwin installed,
-you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""")
+ raise PackagingPlatformError(
+"""Python was built with Visual Studio 2003; extensions must be built with
+a compiler than can generate compatible binaries. Visual Studio 2003 was
+not found on this system. If you have Cygwin installed, you can try
+compiling with MingW32, by passing "-c mingw32" to pysetup.""")
p = r"Software\Microsoft\NET Framework Setup\Product"
for base in HKEYS:
@@ -145,10 +147,11 @@
self.macros["$(FrameworkVersion)"] = d["version"]
def sub(self, s):
- for k, v in self.macros.iteritems():
- s = string.replace(s, k, v)
+ for k, v in self.macros.items():
+ s = s.replace(k, v)
return s
+
def get_build_version():
"""Return the version of MSVC that was used to build Python.
@@ -157,7 +160,7 @@
"""
prefix = "MSC v."
- i = string.find(sys.version, prefix)
+ i = sys.version.find(prefix)
if i == -1:
return 6
i = i + len(prefix)
@@ -172,6 +175,7 @@
# else we don't know what version of the compiler this is
return None
+
def get_build_architecture():
"""Return the processor architecture.
@@ -179,12 +183,13 @@
"""
prefix = " bit ("
- i = string.find(sys.version, prefix)
+ i = sys.version.find(prefix)
if i == -1:
return "Intel"
- j = string.find(sys.version, ")", i)
+ j = sys.version.find(")", i)
return sys.version[i+len(prefix):j]
+
def normalize_and_reduce_paths(paths):
"""Return a list of normalized paths with duplicates removed.
@@ -200,7 +205,7 @@
return reduced_paths
-class MSVCCompiler (CCompiler) :
+class MSVCCompiler(CCompiler):
"""Concrete class that implements an interface to Microsoft Visual C++,
as defined by the CCompiler abstract class."""
@@ -231,8 +236,8 @@
static_lib_format = shared_lib_format = '%s%s'
exe_extension = '.exe'
- def __init__ (self, verbose=0, dry_run=0, force=0):
- CCompiler.__init__ (self, verbose, dry_run, force)
+ def __init__(self, verbose=0, dry_run=False, force=False):
+ CCompiler.__init__(self, verbose, dry_run, force)
self.__version = get_build_version()
self.__arch = get_build_architecture()
if self.__arch == "Intel":
@@ -251,7 +256,8 @@
def initialize(self):
self.__paths = []
- if "DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and self.find_exe("cl.exe"):
+ if ("DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and
+ self.find_exe("cl.exe")):
# Assume that the SDK set up everything alright; don't try to be
# smarter
self.cc = "cl.exe"
@@ -262,11 +268,11 @@
else:
self.__paths = self.get_msvc_paths("path")
- if len (self.__paths) == 0:
- raise DistutilsPlatformError, \
- ("Python was built with %s, "
- "and extensions need to be built with the same "
- "version of the compiler, but it isn't installed." % self.__product)
+ if len(self.__paths) == 0:
+ raise PackagingPlatformError("Python was built with %s "
+ "and extensions need to be built with the same "
+ "version of the compiler, but it isn't installed." %
+ self.__product)
self.cc = self.find_exe("cl.exe")
self.linker = self.find_exe("link.exe")
@@ -278,23 +284,23 @@
# extend the MSVC path with the current path
try:
- for p in string.split(os.environ['path'], ';'):
+ for p in os.environ['path'].split(';'):
self.__paths.append(p)
except KeyError:
pass
self.__paths = normalize_and_reduce_paths(self.__paths)
- os.environ['path'] = string.join(self.__paths, ';')
+ os.environ['path'] = ';'.join(self.__paths)
self.preprocess_options = None
if self.__arch == "Intel":
- self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', '/GX' ,
- '/DNDEBUG']
+ self.compile_options = ['/nologo', '/Ox', '/MD', '/W3', '/GX',
+ '/DNDEBUG']
self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GX',
'/Z7', '/D_DEBUG']
else:
# Win64
- self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', '/GS-' ,
- '/DNDEBUG']
+ self.compile_options = ['/nologo', '/Ox', '/MD', '/W3', '/GS-',
+ '/DNDEBUG']
self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-',
'/Z7', '/D_DEBUG']
@@ -313,50 +319,46 @@
# -- Worker methods ------------------------------------------------
- def object_filenames (self,
- source_filenames,
- strip_dir=0,
- output_dir=''):
+ def object_filenames(self, source_filenames, strip_dir=False, output_dir=''):
# Copied from ccompiler.py, extended to return .res as 'object'-file
# for .rc input file
- if output_dir is None: output_dir = ''
+ if output_dir is None:
+ output_dir = ''
obj_names = []
for src_name in source_filenames:
- (base, ext) = os.path.splitext (src_name)
- base = os.path.splitdrive(base)[1] # Chop off the drive
+ base, ext = os.path.splitext(src_name)
+ base = os.path.splitdrive(base)[1] # Chop off the drive
base = base[os.path.isabs(base):] # If abs, chop off leading /
if ext not in self.src_extensions:
# Better to raise an exception instead of silently continuing
# and later complain about sources and targets having
# different lengths
- raise CompileError ("Don't know how to compile %s" % src_name)
+ raise CompileError("Don't know how to compile %s" % src_name)
if strip_dir:
- base = os.path.basename (base)
+ base = os.path.basename(base)
if ext in self._rc_extensions:
- obj_names.append (os.path.join (output_dir,
- base + self.res_extension))
+ obj_names.append(os.path.join(output_dir,
+ base + self.res_extension))
elif ext in self._mc_extensions:
- obj_names.append (os.path.join (output_dir,
- base + self.res_extension))
+ obj_names.append(os.path.join(output_dir,
+ base + self.res_extension))
else:
- obj_names.append (os.path.join (output_dir,
- base + self.obj_extension))
+ obj_names.append(os.path.join(output_dir,
+ base + self.obj_extension))
return obj_names
- # object_filenames ()
-
-
def compile(self, sources,
- output_dir=None, macros=None, include_dirs=None, debug=0,
+ output_dir=None, macros=None, include_dirs=None, debug=False,
extra_preargs=None, extra_postargs=None, depends=None):
- if not self.initialized: self.initialize()
+ if not self.initialized:
+ self.initialize()
macros, objects, extra_postargs, pp_opts, build = \
self._setup_compile(output_dir, macros, include_dirs, sources,
depends, extra_postargs)
compile_opts = extra_preargs or []
- compile_opts.append ('/c')
+ compile_opts.append('/c')
if debug:
compile_opts.extend(self.compile_options_debug)
else:
@@ -382,10 +384,10 @@
input_opt = src
output_opt = "/fo" + obj
try:
- self.spawn ([self.rc] + pp_opts +
- [output_opt] + [input_opt])
- except DistutilsExecError, msg:
- raise CompileError, msg
+ self.spawn([self.rc] + pp_opts +
+ [output_opt] + [input_opt])
+ except PackagingExecError:
+ raise CompileError(sys.exc_info()[1])
continue
elif ext in self._mc_extensions:
@@ -401,97 +403,78 @@
# the build directory for the RC file and message
# resources. This works at least for win32all.
- h_dir = os.path.dirname (src)
- rc_dir = os.path.dirname (obj)
+ h_dir = os.path.dirname(src)
+ rc_dir = os.path.dirname(obj)
try:
# first compile .MC to .RC and .H file
- self.spawn ([self.mc] +
- ['-h', h_dir, '-r', rc_dir] + [src])
- base, _ = os.path.splitext (os.path.basename (src))
- rc_file = os.path.join (rc_dir, base + '.rc')
+ self.spawn([self.mc] +
+ ['-h', h_dir, '-r', rc_dir] + [src])
+ base, _ = os.path.splitext(os.path.basename(src))
+ rc_file = os.path.join(rc_dir, base + '.rc')
# then compile .RC to .RES file
- self.spawn ([self.rc] +
+ self.spawn([self.rc] +
["/fo" + obj] + [rc_file])
- except DistutilsExecError, msg:
- raise CompileError, msg
+ except PackagingExecError:
+ raise CompileError(sys.exc_info()[1])
continue
else:
# how to handle this file?
- raise CompileError (
- "Don't know how to compile %s to %s" % \
+ raise CompileError(
+ "Don't know how to compile %s to %s" %
(src, obj))
output_opt = "/Fo" + obj
try:
- self.spawn ([self.cc] + compile_opts + pp_opts +
- [input_opt, output_opt] +
- extra_postargs)
- except DistutilsExecError, msg:
- raise CompileError, msg
+ self.spawn([self.cc] + compile_opts + pp_opts +
+ [input_opt, output_opt] +
+ extra_postargs)
+ except PackagingExecError:
+ raise CompileError(sys.exc_info()[1])
return objects
- # compile ()
+ def create_static_lib(self, objects, output_libname, output_dir=None,
+ debug=False, target_lang=None):
+ if not self.initialized:
+ self.initialize()
+ objects, output_dir = self._fix_object_args(objects, output_dir)
+ output_filename = \
+ self.library_filename(output_libname, output_dir=output_dir)
-
- def create_static_lib (self,
- objects,
- output_libname,
- output_dir=None,
- debug=0,
- target_lang=None):
-
- if not self.initialized: self.initialize()
- (objects, output_dir) = self._fix_object_args (objects, output_dir)
- output_filename = \
- self.library_filename (output_libname, output_dir=output_dir)
-
- if self._need_link (objects, output_filename):
+ if self._need_link(objects, output_filename):
lib_args = objects + ['/OUT:' + output_filename]
if debug:
pass # XXX what goes here?
try:
- self.spawn ([self.lib] + lib_args)
- except DistutilsExecError, msg:
- raise LibError, msg
+ self.spawn([self.lib] + lib_args)
+ except PackagingExecError:
+ raise LibError(sys.exc_info()[1])
else:
logger.debug("skipping %s (up-to-date)", output_filename)
- # create_static_lib ()
+ def link(self, target_desc, objects, output_filename, output_dir=None,
+ libraries=None, library_dirs=None, runtime_library_dirs=None,
+ export_symbols=None, debug=False, extra_preargs=None,
+ extra_postargs=None, build_temp=None, target_lang=None):
- def link (self,
- target_desc,
- objects,
- output_filename,
- output_dir=None,
- libraries=None,
- library_dirs=None,
- runtime_library_dirs=None,
- export_symbols=None,
- debug=0,
- extra_preargs=None,
- extra_postargs=None,
- build_temp=None,
- target_lang=None):
-
- if not self.initialized: self.initialize()
- (objects, output_dir) = self._fix_object_args (objects, output_dir)
- (libraries, library_dirs, runtime_library_dirs) = \
- self._fix_lib_args (libraries, library_dirs, runtime_library_dirs)
+ if not self.initialized:
+ self.initialize()
+ objects, output_dir = self._fix_object_args(objects, output_dir)
+ libraries, library_dirs, runtime_library_dirs = \
+ self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)
if runtime_library_dirs:
- self.warn ("I don't know what to do with 'runtime_library_dirs': "
- + str (runtime_library_dirs))
+ self.warn("don't know what to do with 'runtime_library_dirs': %s"
+ % (runtime_library_dirs,))
- lib_opts = gen_lib_options (self,
- library_dirs, runtime_library_dirs,
- libraries)
+ lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs,
+ libraries)
if output_dir is not None:
- output_filename = os.path.join (output_dir, output_filename)
+ output_filename = os.path.join(output_dir, output_filename)
- if self._need_link (objects, output_filename):
+ if self._need_link(objects, output_filename):
if target_desc == CCompiler.EXECUTABLE:
if debug:
@@ -517,46 +500,41 @@
# directory. Since they have different names for debug and release
# builds, they can go into the same directory.
if export_symbols is not None:
- (dll_name, dll_ext) = os.path.splitext(
+ dll_name, dll_ext = os.path.splitext(
os.path.basename(output_filename))
implib_file = os.path.join(
os.path.dirname(objects[0]),
self.library_filename(dll_name))
- ld_args.append ('/IMPLIB:' + implib_file)
+ ld_args.append('/IMPLIB:' + implib_file)
if extra_preargs:
ld_args[:0] = extra_preargs
if extra_postargs:
ld_args.extend(extra_postargs)
- self.mkpath (os.path.dirname (output_filename))
+ self.mkpath(os.path.dirname(output_filename))
try:
- self.spawn ([self.linker] + ld_args)
- except DistutilsExecError, msg:
- raise LinkError, msg
+ self.spawn([self.linker] + ld_args)
+ except PackagingExecError:
+ raise LinkError(sys.exc_info()[1])
else:
logger.debug("skipping %s (up-to-date)", output_filename)
- # link ()
-
-
# -- Miscellaneous methods -----------------------------------------
# These are all used by the 'gen_lib_options() function, in
# ccompiler.py.
- def library_dir_option (self, dir):
+ def library_dir_option(self, dir):
return "/LIBPATH:" + dir
- def runtime_library_dir_option (self, dir):
- raise DistutilsPlatformError, \
- "don't know how to set runtime library search path for MSVC++"
+ def runtime_library_dir_option(self, dir):
+ raise PackagingPlatformError("don't know how to set runtime library search path for MSVC++")
- def library_option (self, lib):
- return self.library_filename (lib)
+ def library_option(self, lib):
+ return self.library_filename(lib)
-
- def find_library_file (self, dirs, lib, debug=0):
+ def find_library_file(self, dirs, lib, debug=False):
# Prefer a debugging library if found (and requested), but deal
# with it if we don't have one.
if debug:
@@ -565,15 +543,13 @@
try_names = [lib]
for dir in dirs:
for name in try_names:
- libfile = os.path.join(dir, self.library_filename (name))
+ libfile = os.path.join(dir, self.library_filename(name))
if os.path.exists(libfile):
return libfile
else:
# Oops, didn't find it in *any* of 'dirs'
return None
- # find_library_file ()
-
# Helper methods for using the MSVC registry settings
def find_exe(self, exe):
@@ -592,8 +568,8 @@
return fn
# didn't find it; try existing path
- for p in string.split(os.environ['Path'],';'):
- fn = os.path.join(os.path.abspath(p),exe)
+ for p in os.environ['Path'].split(';'):
+ fn = os.path.join(os.path.abspath(p), exe)
if os.path.isfile(fn):
return fn
@@ -621,9 +597,9 @@
d = read_values(base, key)
if d:
if self.__version >= 7:
- return string.split(self.__macros.sub(d[path]), ";")
+ return self.__macros.sub(d[path]).split(";")
else:
- return string.split(d[path], ";")
+ return d[path].split(";")
# MSVC 6 seems to create the registry entries we need only when
# the GUI is run.
if self.__version == 6:
@@ -648,7 +624,7 @@
else:
p = self.get_msvc_paths(name)
if p:
- os.environ[name] = string.join(p, ';')
+ os.environ[name] = ';'.join(p)
if get_build_version() >= 8.0:
diff --git a/distutils2/compiler/unixccompiler.py b/distutils2/compiler/unixccompiler.py
--- a/distutils2/compiler/unixccompiler.py
+++ b/distutils2/compiler/unixccompiler.py
@@ -1,7 +1,7 @@
-"""distutils.unixccompiler
+"""CCompiler implementation for Unix compilers.
-Contains the UnixCCompiler class, a subclass of CCompiler that handles
-the "typical" Unix-style command-line C compiler:
+This module contains the UnixCCompiler class, a subclass of CCompiler
+that handles the "typical" Unix-style command-line C compiler:
* macros defined with -Dname[=value]
* macros undefined with -Uname
* include search directories specified with -Idir
@@ -13,16 +13,15 @@
* link shared library handled by 'cc -shared'
"""
-
import os, sys
from distutils2.util import newer
from distutils2.compiler.ccompiler import CCompiler
from distutils2.compiler import gen_preprocess_options, gen_lib_options
-from distutils2.errors import (DistutilsExecError, CompileError,
+from distutils2.errors import (PackagingExecError, CompileError,
LibError, LinkError)
from distutils2 import logger
-from distutils2._backport import sysconfig
+import sysconfig
# XXX Things not currently handled:
@@ -34,7 +33,7 @@
# we need some way for outsiders to feed preprocessor/compiler/linker
# flags in to us -- eg. a sysadmin might want to mandate certain flags
# via a site config file, or a user might want to set something for
-# compiling this module distribution only via the setup.py command
+# compiling this module distribution only via the pysetup command
# line, whatever. As long as these options come from something on the
# current system, they can be as system-dependent as they like, and we
# should just happily stuff them into the preprocessor/compiler/linker
@@ -49,7 +48,7 @@
build, without a way to remove an architecture. Furthermore GCC will
barf if multiple '-isysroot' arguments are present.
"""
- stripArch = stripSysroot = 0
+ stripArch = stripSysroot = False
compiler_so = list(compiler_so)
kernel_version = os.uname()[2] # 8.4.3
@@ -64,7 +63,7 @@
stripSysroot = '-isysroot' in cc_args
if stripArch or 'ARCHFLAGS' in os.environ:
- while 1:
+ while True:
try:
index = compiler_so.index('-arch')
# Strip this argument and the next one:
@@ -150,7 +149,7 @@
pp_opts = gen_preprocess_options(macros, include_dirs)
pp_args = self.preprocessor + pp_opts
if output_file:
- pp_args.extend(['-o', output_file])
+ pp_args.extend(('-o', output_file))
if extra_preargs:
pp_args[:0] = extra_preargs
if extra_postargs:
@@ -166,8 +165,8 @@
self.mkpath(os.path.dirname(output_file))
try:
self.spawn(pp_args)
- except DistutilsExecError, msg:
- raise CompileError, msg
+ except PackagingExecError:
+ raise CompileError(sys.exc_info()[1])
def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
compiler_so = self.compiler_so
@@ -176,11 +175,11 @@
try:
self.spawn(compiler_so + cc_args + [src, '-o', obj] +
extra_postargs)
- except DistutilsExecError, msg:
- raise CompileError, msg
+ except PackagingExecError:
+ raise CompileError(sys.exc_info()[1])
def create_static_lib(self, objects, output_libname,
- output_dir=None, debug=0, target_lang=None):
+ output_dir=None, debug=False, target_lang=None):
objects, output_dir = self._fix_object_args(objects, output_dir)
output_filename = \
@@ -200,15 +199,15 @@
if self.ranlib:
try:
self.spawn(self.ranlib + [output_filename])
- except DistutilsExecError, msg:
- raise LibError, msg
+ except PackagingExecError:
+ raise LibError(sys.exc_info()[1])
else:
logger.debug("skipping %s (up-to-date)", output_filename)
def link(self, target_desc, objects,
output_filename, output_dir=None, libraries=None,
library_dirs=None, runtime_library_dirs=None,
- export_symbols=None, debug=0, extra_preargs=None,
+ export_symbols=None, debug=False, extra_preargs=None,
extra_postargs=None, build_temp=None, target_lang=None):
objects, output_dir = self._fix_object_args(objects, output_dir)
libraries, library_dirs, runtime_library_dirs = \
@@ -216,8 +215,8 @@
lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs,
libraries)
- if not isinstance(output_dir, (str, type(None))):
- raise TypeError, "'output_dir' must be a string or None"
+ if type(output_dir) not in (str, type(None)):
+ raise TypeError("'output_dir' must be a string or None")
if output_dir is not None:
output_filename = os.path.join(output_dir, output_filename)
@@ -254,8 +253,8 @@
linker = _darwin_compiler_fixup(linker, ld_args)
self.spawn(linker + ld_args)
- except DistutilsExecError, msg:
- raise LinkError, msg
+ except PackagingExecError:
+ raise LinkError(sys.exc_info()[1])
else:
logger.debug("skipping %s (up-to-date)", output_filename)
@@ -316,7 +315,7 @@
def library_option(self, lib):
return "-l" + lib
- def find_library_file(self, dirs, lib, debug=0):
+ def find_library_file(self, dirs, lib, debug=False):
shared_f = self.library_filename(lib, lib_type='shared')
dylib_f = self.library_filename(lib, lib_type='dylib')
static_f = self.library_filename(lib, lib_type='static')
diff --git a/distutils2/config.py b/distutils2/config.py
--- a/distutils2/config.py
+++ b/distutils2/config.py
@@ -1,21 +1,19 @@
-""" distutil2.config
+"""Utilities to find and read config files used by distutils2."""
- Know how to read all config files Distutils2 uses.
-"""
-import os.path
+import codecs
import os
import sys
import logging
+
+from shlex import split
from ConfigParser import RawConfigParser
-from shlex import split
-
from distutils2 import logger
-from distutils2.errors import DistutilsOptionError
+from distutils2.errors import PackagingOptionError
from distutils2.compiler.extension import Extension
-from distutils2.util import check_environ, resolve_name, strtobool
+from distutils2.util import (check_environ, iglob, resolve_name, strtobool,
+ split_multiline)
from distutils2.compiler import set_compiler
from distutils2.command import set_command
-from distutils2.resources import resources_dests
from distutils2.markers import interpret
@@ -25,29 +23,54 @@
if not vals_str:
return
fields = []
- for field in vals_str.split(os.linesep):
+ # the line separator is \n for setup.cfg files
+ for field in vals_str.split('\n'):
tmp_vals = field.split('--')
- if (len(tmp_vals) == 2) and (not interpret(tmp_vals[1])):
+ if len(tmp_vals) == 2 and not interpret(tmp_vals[1]):
continue
fields.append(tmp_vals[0])
- # Get bash options like `gcc -print-file-name=libgcc.a`
+ # Get bash options like `gcc -print-file-name=libgcc.a` XXX bash options?
vals = split(' '.join(fields))
if vals:
return vals
+def _rel_path(base, path):
+ # normalizes and returns a lstripped-/-separated path
+ base = base.replace(os.path.sep, '/')
+ path = path.replace(os.path.sep, '/')
+ assert path.startswith(base)
+ return path[len(base):].lstrip('/')
+
+
+def get_resources_dests(resources_root, rules):
+ """Find destinations for resources files"""
+ 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 = _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 = _rel_path(abs_base, abs_path)
+ rel_dest = dest.replace(os.path.sep, '/').rstrip('/')
+ destinations[resource_file] = rel_dest + '/' + rel_path
+ return destinations
+
+
class Config(object):
- """Reads configuration files and work with the Distribution instance
- """
+ """Class used to work with configuration files"""
def __init__(self, dist):
self.dist = dist
- self.setup_hook = None
+ self.setup_hooks = []
- def run_hook(self, config):
- if self.setup_hook is None:
- return
- # the hook gets only the config
- self.setup_hook(config)
+ def run_hooks(self, config):
+ """Run setup hooks in the order defined in the spec."""
+ for hook in self.setup_hooks:
+ hook(config)
def find_config_files(self):
"""Find as many configuration files as should be processed for this
@@ -55,9 +78,9 @@
should be parsed. The filenames returned are guaranteed to exist
(modulo nasty race conditions).
- There are three possible config files: distutils.cfg in the
- Distutils installation directory (ie. where the top-level
- Distutils __inst__.py file lives), a file in the user's home
+ There are three possible config files: distutils2.cfg in the
+ Packaging installation directory (ie. where the top-level
+ Packaging __inst__.py file lives), a file in the user's home
directory named .pydistutils.cfg on Unix and pydistutils.cfg
on Windows/Mac; and setup.cfg in the current directory.
@@ -67,11 +90,11 @@
files = []
check_environ()
- # Where to look for the system-wide Distutils config file
+ # Where to look for the system-wide Packaging config file
sys_dir = os.path.dirname(sys.modules['distutils2'].__file__)
# Look for the system config file
- sys_file = os.path.join(sys_dir, "distutils.cfg")
+ sys_file = os.path.join(sys_dir, "distutils2.cfg")
if os.path.isfile(sys_file):
files.append(sys_file)
@@ -101,33 +124,41 @@
# XXX
return value
- def _multiline(self, value):
- value = [v for v in
- [v.strip() for v in value.split('\n')]
- if v != '']
- return value
-
def _read_setup_cfg(self, parser, cfg_filename):
cfg_directory = os.path.dirname(os.path.abspath(cfg_filename))
content = {}
for section in parser.sections():
content[section] = dict(parser.items(section))
- # global:setup_hook is called *first*
+ # global setup hooks are called first
if 'global' in content:
- if 'setup_hook' in content['global']:
- setup_hook = content['global']['setup_hook']
- self.setup_hook = resolve_name(setup_hook)
- self.run_hook(content)
+ if 'setup_hooks' in content['global']:
+ setup_hooks = split_multiline(content['global']['setup_hooks'])
+
+ # add project directory to sys.path, to allow hooks to be
+ # distributed with the project
+ sys.path.insert(0, cfg_directory)
+ try:
+ for line in setup_hooks:
+ try:
+ hook = resolve_name(line)
+ except ImportError:
+ logger.warning('cannot find setup hook: %s',
+ sys.exc_info()[1].args[0])
+ else:
+ self.setup_hooks.append(hook)
+ self.run_hooks(content)
+ finally:
+ sys.path.pop(0)
metadata = self.dist.metadata
# setting the metadata values
if 'metadata' in content:
- for key, value in content['metadata'].iteritems():
+ for key, value in content['metadata'].items():
key = key.replace('_', '-')
if metadata.is_multi_field(key):
- value = self._multiline(value)
+ value = split_multiline(value)
if key == 'project-url':
value = [(label.strip(), url.strip())
@@ -138,71 +169,74 @@
if 'description' in content['metadata']:
msg = ("description and description-file' are "
"mutually exclusive")
- raise DistutilsOptionError(msg)
+ raise PackagingOptionError(msg)
- if isinstance(value, list):
- filenames = value
- else:
- filenames = value.split()
+ filenames = value.split()
- # concatenate each files
- value = ''
+ # concatenate all files
+ value = []
for filename in filenames:
# will raise if file not found
- description_file = open(filename)
- try:
- value += description_file.read().strip() + '\n'
- finally:
- description_file.close()
+ with open(filename) as description_file:
+ value.append(description_file.read().strip())
# add filename as a required file
if filename not in metadata.requires_files:
metadata.requires_files.append(filename)
- value = value.strip()
+ value = '\n'.join(value).strip()
key = 'description'
if metadata.is_metadata_field(key):
metadata[key] = self._convert_metadata(key, value)
+ if 'files' in content:
+ files = content['files']
+ self.dist.package_dir = files.pop('packages_root', None)
- if 'files' in content:
- def _convert(key, value):
- if key not in ('packages_root',):
- value = self._multiline(value)
- return value
+ files = dict((key, split_multiline(value)) for key, value in
+ files.items())
- files = dict([(key, _convert(key, value))
- for key, value in content['files'].iteritems()])
self.dist.packages = []
- self.dist.package_dir = files.get('packages_root')
packages = files.get('packages', [])
- if isinstance(packages, str):
+ if isinstance(packages, basestring):
packages = [packages]
for package in packages:
+ if ':' in package:
+ dir_, package = package.split(':')
+ self.dist.package_dir[package] = dir_
self.dist.packages.append(package)
self.dist.py_modules = files.get('modules', [])
- if isinstance(self.dist.py_modules, str):
+ if isinstance(self.dist.py_modules, basestring):
self.dist.py_modules = [self.dist.py_modules]
self.dist.scripts = files.get('scripts', [])
- if isinstance(self.dist.scripts, str):
+ if isinstance(self.dist.scripts, basestring):
self.dist.scripts = [self.dist.scripts]
self.dist.package_data = {}
for data in files.get('package_data', []):
data = data.split('=')
if len(data) != 2:
- continue # XXX error should never pass silently
+ continue # XXX error should never pass silently
key, value = data
self.dist.package_data[key.strip()] = value.strip()
+ self.dist.data_files = []
+ for data in files.get('data_files', []):
+ data = data.split('=')
+ if len(data) != 2:
+ continue
+ key, value = data
+ values = [v.strip() for v in value.split(',')]
+ self.dist.data_files.append((key, values))
+
# manifest template
self.dist.extra_files = files.get('extra_files', [])
resources = []
for rule in files.get('resources', []):
- glob , destination = rule.split('=', 1)
+ glob, destination = rule.split('=', 1)
rich_glob = glob.strip().split(' ', 1)
if len(rich_glob) == 2:
prefix, suffix = rich_glob
@@ -212,13 +246,15 @@
suffix = glob
if destination == '<exclude>':
destination = None
- resources.append((prefix.strip(), suffix.strip(), destination.strip()))
- self.dist.data_files = resources_dests(cfg_directory, resources)
+ resources.append(
+ (prefix.strip(), suffix.strip(), destination.strip()))
+ self.dist.data_files = get_resources_dests(
+ cfg_directory, resources)
ext_modules = self.dist.ext_modules
for section_key in content:
labels = section_key.split('=')
- if (len(labels) == 2) and (labels[0] == 'extension'):
+ if len(labels) == 2 and labels[0] == 'extension':
# labels[1] not used from now but should be implemented
# for extension build dependency
values_dct = content[section_key]
@@ -239,8 +275,7 @@
_pop_values(values_dct, 'depends'),
values_dct.pop('language', None),
values_dct.pop('optional', None),
- **values_dct
- ))
+ **values_dct))
def parse_config_files(self, filenames=None):
if filenames is None:
@@ -252,7 +287,8 @@
for filename in filenames:
logger.debug(" reading %s", filename)
- parser.read(filename)
+ with codecs.open(filename, 'r', encoding='utf-8') as f:
+ parser.readfp(f)
if os.path.split(filename)[-1] == 'setup.cfg':
self._read_setup_cfg(parser, filename)
@@ -275,8 +311,8 @@
opt = opt.replace('-', '_')
if opt == 'sub_commands':
- val = self._multiline(val)
- if isinstance(val, str):
+ val = split_multiline(val)
+ if isinstance(val, basestring):
val = [val]
# Hooks use a suffix system to prevent being overriden
@@ -287,8 +323,8 @@
if (opt.startswith("pre_hook.") or
opt.startswith("post_hook.")):
hook_type, alias = opt.split(".")
- hook_dict = opt_dict.setdefault(hook_type,
- (filename, {}))[1]
+ hook_dict = opt_dict.setdefault(
+ hook_type, (filename, {}))[1]
hook_dict[alias] = val
else:
opt_dict[opt] = filename, val
@@ -300,28 +336,28 @@
# If there was a "global" section in the config file, use it
# to set Distribution options.
if 'global' in self.dist.command_options:
- for (opt, (src, val)) in self.dist.command_options['global'].iteritems():
+ for opt, (src, val) in self.dist.command_options['global'].items():
alias = self.dist.negative_opt.get(opt)
try:
if alias:
setattr(self.dist, alias, not strtobool(val))
- elif opt in ('verbose', 'dry_run'): # ugh!
+ elif opt == 'dry_run': # FIXME ugh!
setattr(self.dist, opt, strtobool(val))
else:
setattr(self.dist, opt, val)
- except ValueError, msg:
- raise DistutilsOptionError(msg)
+ except ValueError:
+ raise PackagingOptionError(sys.exc_info()[1])
def _load_compilers(self, compilers):
- compilers = self._multiline(compilers)
- if isinstance(compilers, str):
+ compilers = split_multiline(compilers)
+ if isinstance(compilers, basestring):
compilers = [compilers]
for compiler in compilers:
set_compiler(compiler.strip())
def _load_commands(self, commands):
- commands = self._multiline(commands)
- if isinstance(commands, str):
+ commands = split_multiline(commands)
+ if isinstance(commands, basestring):
commands = [commands]
for command in commands:
set_command(command.strip())
diff --git a/distutils2/create.py b/distutils2/create.py
--- a/distutils2/create.py
+++ b/distutils2/create.py
@@ -1,17 +1,12 @@
-#!/usr/bin/env python
-#
-# Helper for automating the creation of a package by looking at you
-# current directory and asking the user questions.
-#
-# Available as either a stand-alone file or callable from the distutils2
-# package:
-#
-# python -m distutils2.mkcfg
-# or:
-# python mkcfg.py
-#
-# Written by Sean Reifschneider <jafo(a)tummy.com>
-#
+"""Interactive helper used to create a setup.cfg file.
+
+This script will generate a distutils2 configuration file by looking at
+the current directory and asking the user questions. It is intended to
+be called as *pysetup create*.
+"""
+
+# Original code by Sean Reifschneider <jafo(a)tummy.com>
+
# Original TODO list:
# Look for a license file and automatically add the category.
# When a .c file is found during the walk, can we add it as an extension?
@@ -23,23 +18,26 @@
# Ask for a description
# Detect scripts (not sure how. #! outside of package?)
+import codecs
import os
+import re
+import imp
import sys
import glob
-import re
import shutil
+import sysconfig
+import tokenize
+from hashlib import md5
+from textwrap import dedent
+from distutils2.util import cmp_to_key, detect_encoding
from ConfigParser import RawConfigParser
-from textwrap import dedent
-try:
- from hashlib import md5
-except ImportError:
- from distutils2._backport.hashlib import md5
# importing this with an underscore as it should be replaced by the
# dict form or another structures for all purposes
from distutils2._trove import all_classifiers as _CLASSIFIERS_LIST
-from distutils2._backport import sysconfig
+from distutils2.version import is_valid_version
_FILENAME = 'setup.cfg'
+_DEFAULT_CFG = '.pypkgcreate'
_helptext = {
'name': '''
@@ -64,7 +62,7 @@
'do_classifier': '''
Trove classifiers are optional identifiers that allow you to specify the
intended audience by saying things like "Beta software with a text UI
-for Linux under the PSF license. However, this can be a somewhat involved
+for Linux under the PSF license". However, this can be a somewhat involved
process.
''',
'packages': '''
@@ -91,14 +89,34 @@
''',
'setup.py found': '''
The setup.py script will be executed to retrieve the metadata.
-A wizard will be run if you answer "n",
+An interactive helper will be run if you answer "n",
''',
}
+PROJECT_MATURITY = ['Development Status :: 1 - Planning',
+ 'Development Status :: 2 - Pre-Alpha',
+ 'Development Status :: 3 - Alpha',
+ 'Development Status :: 4 - Beta',
+ 'Development Status :: 5 - Production/Stable',
+ 'Development Status :: 6 - Mature',
+ 'Development Status :: 7 - Inactive']
+
# XXX everything needs docstrings and tests (both low-level tests of various
# methods and functional tests of running the script)
+def load_setup():
+ """run the setup script (i.e the setup.py file)
+
+ This function load the setup file in all cases (even if it have already
+ been loaded before, because we are monkey patching its setup function with
+ a particular one"""
+ with open("setup.py", "rb") as f:
+ encoding, lines = detect_encoding(f.readline)
+ with open("setup.py") as f:
+ imp.load_module("setup", f, "setup.py", (".py", "r", imp.PY_SOURCE))
+
+
def ask_yn(question, default=None, helptext=None):
question += ' (y/n)'
while True:
@@ -106,16 +124,16 @@
if answer and answer[0].lower() in 'yn':
return answer[0].lower()
- print '\nERROR: You must select "Y" or "N".\n'
+ print('\nERROR: You must select "Y" or "N".\n')
def ask(question, default=None, helptext=None, required=True,
lengthy=False, multiline=False):
- prompt = '%s: ' % (question,)
+ prompt = u'%s: ' % (question,)
if default:
- prompt = '%s [%s]: ' % (question, default)
+ prompt = u'%s [%s]: ' % (question, default)
if default and len(question) + len(default) > 70:
- prompt = '%s\n [%s]: ' % (question, default)
+ prompt = u'%s\n [%s]: ' % (question, default)
if lengthy or multiline:
prompt += '\n > '
@@ -130,31 +148,39 @@
line = sys.stdin.readline().strip()
if line == '?':
- print '=' * 70
- print helptext
- print '=' * 70
+ print('=' * 70)
+ print(helptext)
+ print('=' * 70)
continue
if default and not line:
return default
if not line and required:
- print '*' * 70
- print 'This value cannot be empty.'
- print '==========================='
+ print('*' * 70)
+ print('This value cannot be empty.')
+ print('===========================')
if helptext:
- print helptext
- print '*' * 70
+ print(helptext)
+ print('*' * 70)
continue
return line
+def convert_yn_to_bool(yn, yes=True, no=False):
+ """Convert a y/yes or n/no to a boolean value."""
+ if yn.lower().startswith('y'):
+ return yes
+ else:
+ return no
+
+
def _build_classifiers_dict(classifiers):
d = {}
for key in classifiers:
- subDict = d
+ subdict = d
for subkey in key.split(' :: '):
- if not subkey in subDict:
- subDict[subkey] = {}
- subDict = subDict[subkey]
+ if subkey not in subdict:
+ subdict[subkey] = {}
+ subdict = subdict[subkey]
return d
CLASSIFIERS = _build_classifiers_dict(_CLASSIFIERS_LIST)
@@ -172,78 +198,144 @@
class MainProgram(object):
+ """Make a project setup configuration file (setup.cfg)."""
+
def __init__(self):
self.configparser = None
- self.classifiers = set([])
- self.data = {}
- self.data['classifier'] = self.classifiers
- self.data['packages'] = []
- self.data['modules'] = []
- self.data['platform'] = []
- self.data['resources'] = []
- self.data['extra_files'] = []
- self.data['scripts'] = []
- self.load_config_file()
+ self.classifiers = set()
+ self.data = {'name': '',
+ 'version': '1.0.0',
+ 'classifier': self.classifiers,
+ 'packages': [],
+ 'modules': [],
+ 'platform': [],
+ 'resources': [],
+ 'extra_files': [],
+ 'scripts': [],
+ }
+ self._load_defaults()
- def lookup_option(self, key):
+ def __call__(self):
+ setupcfg_defined = False
+ if self.has_setup_py() and self._prompt_user_for_conversion():
+ setupcfg_defined = self.convert_py_to_cfg()
+ if not setupcfg_defined:
+ self.define_cfg_values()
+ self._write_cfg()
+
+ def has_setup_py(self):
+ """Test for the existence of a setup.py file."""
+ return os.path.exists('setup.py')
+
+ def define_cfg_values(self):
+ self.inspect()
+ self.query_user()
+
+ def _lookup_option(self, key):
if not self.configparser.has_option('DEFAULT', key):
return None
return self.configparser.get('DEFAULT', key)
- def load_config_file(self):
+ def _load_defaults(self):
+ # Load default values from a user configuration file
self.configparser = RawConfigParser()
# TODO replace with section in distutils config file
- #XXX freedesktop
- self.configparser.read(os.path.expanduser('~/.mkcfg'))
- self.data['author'] = self.lookup_option('author')
- self.data['author_email'] = self.lookup_option('author_email')
+ default_cfg = os.path.expanduser(os.path.join('~', _DEFAULT_CFG))
+ self.configparser.read(default_cfg)
+ self.data['author'] = self._lookup_option('author')
+ self.data['author_email'] = self._lookup_option('author_email')
- def update_config_file(self):
- valuesDifferent = False
- # FIXME looking only for those two fields seems wrong
- for compareKey in ('author', 'author_email'):
- if self.lookup_option(compareKey) != self.data[compareKey]:
- valuesDifferent = True
- self.configparser.set('DEFAULT', compareKey,
- self.data[compareKey])
+ def _prompt_user_for_conversion(self):
+ # Prompt the user about whether they would like to use the setup.py
+ # conversion utility to generate a setup.cfg or generate the setup.cfg
+ # from scratch
+ answer = ask_yn(('A legacy setup.py has been found.\n'
+ 'Would you like to convert it to a setup.cfg?'),
+ default="y",
+ helptext=_helptext['setup.py found'])
+ return convert_yn_to_bool(answer)
- if not valuesDifferent:
- return
+ def _dotted_packages(self, data):
+ packages = sorted(data)
+ modified_pkgs = []
+ for pkg in packages:
+ pkg = pkg.lstrip('./')
+ pkg = pkg.replace('/', '.')
+ modified_pkgs.append(pkg)
+ return modified_pkgs
- #XXX freedesktop
- fp = open(os.path.expanduser('~/.mkcfgpy'), 'w')
- try:
- self.configparser.write(fp)
- finally:
- fp.close()
+ def _write_cfg(self):
+ if os.path.exists(_FILENAME):
+ if os.path.exists('%s.old' % _FILENAME):
+ print("ERROR: %(name)s.old backup exists, please check that "
+ "current %(name)s is correct and remove %(name)s.old" %
+ {'name': _FILENAME})
+ return
+ shutil.move(_FILENAME, '%s.old' % _FILENAME)
- def load_existing_setup_script(self):
- """ Generate a setup.cfg from an existing setup.py.
+ with codecs.open(_FILENAME, 'w', encoding='utf-8') as fp:
+ fp.write('[metadata]\n')
+ # TODO use metadata module instead of hard-coding field-specific
+ # behavior here
+
+ # simple string entries
+ for name in ('name', 'version', 'summary', 'download_url'):
+ fp.write('%s = %s\n' % (name, self.data.get(name, 'UNKNOWN')))
+
+ # optional string entries
+ if 'keywords' in self.data and self.data['keywords']:
+ fp.write('keywords = %s\n' % ' '.join(self.data['keywords']))
+ for name in ('home_page', 'author', 'author_email',
+ 'maintainer', 'maintainer_email', 'description-file'):
+ if name in self.data and self.data[name]:
+ fp.write('%s = %s\n' % (name, self.data[name]))
+ if 'description' in self.data:
+ fp.write(
+ 'description = %s\n'
+ % '\n |'.join(self.data['description'].split('\n')))
+
+ # multiple use string entries
+ for name in ('platform', 'supported-platform', 'classifier',
+ 'requires-dist', 'provides-dist', 'obsoletes-dist',
+ 'requires-external'):
+ if not(name in self.data and self.data[name]):
+ continue
+ fp.write('%s = ' % name)
+ fp.write(''.join(' %s\n' % val
+ for val in self.data[name]).lstrip())
+ fp.write('\n[files]\n')
+ for name in ('packages', 'modules', 'scripts',
+ 'package_data', 'extra_files'):
+ if not(name in self.data and self.data[name]):
+ continue
+ fp.write('%s = %s\n'
+ % (name, '\n '.join(self.data[name]).strip()))
+ fp.write('\nresources =\n')
+ for src, dest in self.data['resources']:
+ fp.write(' %s = %s\n' % (src, dest))
+ fp.write('\n')
+
+ os.chmod(_FILENAME, 0o644)
+ print('Wrote "%s".' % _FILENAME)
+
+ def convert_py_to_cfg(self):
+ """Generate a setup.cfg from an existing setup.py.
It only exports the distutils metadata (setuptools specific metadata
- is not actually supported).
+ is not currently supported).
"""
- setuppath = 'setup.py'
- if not os.path.exists(setuppath):
- return
- else:
- ans = ask_yn(('A legacy setup.py has been found.\n'
- 'Would you like to convert it to a setup.cfg ?'),
- 'y',
- _helptext['setup.py found'])
- if ans != 'y':
- return
-
data = self.data
- def setup(**attrs):
- """Mock the setup(**attrs) in order to retrive metadata."""
- # use the distutils v1 processings to correctly parse metadata.
- #XXX we could also use the setuptools distibution ???
+ def setup_mock(**attrs):
+ """Mock the setup(**attrs) in order to retrieve metadata."""
+
+ # TODO use config and metadata instead of Distribution
from distutils.dist import Distribution
dist = Distribution(attrs)
dist.parse_config_files()
- # 1. retrieves metadata that are quite similar PEP314<->PEP345
+
+ # 1. retrieve metadata fields that are quite similar in
+ # PEP 314 and PEP 345
labels = (('name',) * 2,
('version',) * 2,
('author',) * 2,
@@ -253,83 +345,99 @@
('description', 'summary'),
('long_description', 'description'),
('url', 'home_page'),
- ('platforms', 'platform'))
+ ('platforms', 'platform'),
+ # backport only for 2.5+
+ ('provides', 'provides-dist'),
+ ('obsoletes', 'obsoletes-dist'),
+ ('requires', 'requires-dist'))
- if sys.version[:3] >= '2.5':
- labels += (('provides', 'provides-dist'),
- ('obsoletes', 'obsoletes-dist'),
- ('requires', 'requires-dist'),)
get = lambda lab: getattr(dist.metadata, lab.replace('-', '_'))
- data.update((new, get(old)) for (old, new) in labels if get(old))
- # 2. retrieves data that requires special processings.
+ data.update((new, get(old)) for old, new in labels if get(old))
+
+ # 2. retrieve data that requires special processing
data['classifier'].update(dist.get_classifiers() or [])
data['scripts'].extend(dist.scripts or [])
data['packages'].extend(dist.packages or [])
data['modules'].extend(dist.py_modules or [])
- # 2.1 data_files -> resources.
+ # 2.1 data_files -> resources
if dist.data_files:
- if len(dist.data_files) < 2 or \
- isinstance(dist.data_files[1], str):
+ if (len(dist.data_files) < 2 or
+ isinstance(dist.data_files[1], basestring)):
dist.data_files = [('', dist.data_files)]
# add tokens in the destination paths
vars = {'distribution.name': data['name']}
- path_tokens = sysconfig.get_paths(vars=vars).items()
+ path_tokens = list(sysconfig.get_paths(vars=vars).items())
+
+ # TODO replace this with a key function
+ def length_comparison(x, y):
+ len_x = len(x[1])
+ len_y = len(y[1])
+ if len_x == len_y:
+ return 0
+ elif len_x < len_y:
+ return -1
+ else:
+ return 1
+
# sort tokens to use the longest one first
- # TODO chain two sorted with key arguments, remove cmp
- path_tokens.sort(cmp=lambda x, y: cmp(len(y), len(x)),
- key=lambda x: x[1])
+ path_tokens.sort(key=cmp_to_key(length_comparison))
for dest, srcs in (dist.data_files or []):
dest = os.path.join(sys.prefix, dest)
+ dest = dest.replace(os.path.sep, '/')
for tok, path in path_tokens:
- if dest.startswith(path):
- dest = ('{%s}' % tok) + dest[len(path):]
- files = [('/ '.join(src.rsplit('/', 1)), dest)
- for src in srcs]
- data['resources'].extend(files)
+ path = path.replace(os.path.sep, '/')
+ if not dest.startswith(path):
continue
+
+ dest = ('{%s}' % tok) + dest[len(path):]
+ files = [('/ '.join(src.rsplit('/', 1)), dest)
+ for src in srcs]
+ data['resources'].extend(files)
+
# 2.2 package_data -> extra_files
package_dirs = dist.package_dir or {}
- for package, extras in dist.package_data.iteritems() or []:
+ for package, extras in dist.package_data.items() or []:
package_dir = package_dirs.get(package, package)
- files = [os.path.join(package_dir, f) for f in extras]
- data['extra_files'].extend(files)
+ for file_ in extras:
+ if package_dir:
+ file_ = package_dir + '/' + file_
+ data['extra_files'].append(file_)
# Use README file if its content is the desciption
if "description" in data:
- ref = md5(re.sub('\s', '', self.data['description']).lower())
+ ref = md5(re.sub('\s', '',
+ self.data['description']).lower().encode())
ref = ref.digest()
for readme in glob.glob('README*'):
- fp = open(readme)
- try:
+ with codecs.open(readme, encoding='utf-8') as fp:
contents = fp.read()
- finally:
- fp.close()
- val = md5(re.sub('\s', '', contents.lower())).digest()
+ contents = re.sub('\s', '', contents.lower()).encode()
+ val = md5(contents).digest()
if val == ref:
del data['description']
data['description-file'] = readme
break
# apply monkey patch to distutils (v1) and setuptools (if needed)
- # (abord the feature if distutils v1 has been killed)
+ # (abort the feature if distutils v1 has been killed)
try:
- import distutils.core as DC
- DC.setup # ensure distutils v1
+ from distutils import core
+ core.setup # make sure it's not d2 maskerading as d1
except (ImportError, AttributeError):
return
- saved_setups = [(DC, DC.setup)]
- DC.setup = setup
+ saved_setups = [(core, core.setup)]
+ core.setup = setup_mock
try:
import setuptools
+ except ImportError:
+ pass
+ else:
saved_setups.append((setuptools, setuptools.setup))
- setuptools.setup = setup
- except (ImportError, AttributeError):
- pass
+ setuptools.setup = setup_mock
# get metadata by executing the setup.py with the patched setup(...)
success = False # for python < 2.4
try:
- pyenv = globals().copy()
- execfile(setuppath, pyenv)
+ load_setup()
success = True
finally: # revert monkey patches
for patched_module, original_setup in saved_setups:
@@ -338,33 +446,27 @@
raise ValueError('Unable to load metadata from setup.py')
return success
- def inspect_file(self, path):
- fp = open(path, 'r')
- try:
- for _ in xrange(10):
- line = fp.readline()
- m = re.match(r'^#!.*python((?P<major>\d)(\.\d+)?)?$', line)
- if m:
- if m.group('major') == '3':
- self.classifiers.add(
- 'Programming Language :: Python :: 3')
- else:
- self.classifiers.add(
- 'Programming Language :: Python :: 2')
- finally:
- fp.close()
+ def inspect(self):
+ """Inspect the current working diretory for a name and version.
- def inspect_directory(self):
- dirName = os.path.basename(os.getcwd())
- self.data['name'] = dirName
- m = re.match(r'(.*)-(\d.+)', dirName)
- if m:
- self.data['name'] = m.group(1)
- self.data['version'] = m.group(2)
+ This information is harvested in where the directory is named
+ like [name]-[version].
+ """
+ dir_name = os.path.basename(os.getcwd())
+ self.data['name'] = dir_name
+ match = re.match(r'(.*)-(\d.+)', dir_name)
+ if match:
+ self.data['name'] = match.group(1)
+ self.data['version'] = match.group(2)
+ # TODO needs testing!
+ if not is_valid_version(self.data['version']):
+ msg = "Invalid version discovered: %s" % self.data['version']
+ raise ValueError(msg)
def query_user(self):
self.data['name'] = ask('Project name', self.data['name'],
_helptext['name'])
+
self.data['version'] = ask('Current version number',
self.data.get('version'), _helptext['version'])
self.data['summary'] = ask('Package summary',
@@ -374,25 +476,25 @@
self.data.get('author'), _helptext['author'])
self.data['author_email'] = ask('Author e-mail address',
self.data.get('author_email'), _helptext['author_email'])
- self.data['home_page'] = ask('Project Home Page',
+ self.data['home_page'] = ask('Project home page',
self.data.get('home_page'), _helptext['home_page'],
required=False)
if ask_yn('Do you want me to automatically build the file list '
- 'with everything I can find in the current directory ? '
+ 'with everything I can find in the current directory? '
'If you say no, you will have to define them manually.') == 'y':
self._find_files()
else:
- while ask_yn('Do you want to add a single module ?'
+ while ask_yn('Do you want to add a single module?'
' (you will be able to add full packages next)',
helptext=_helptext['modules']) == 'y':
self._set_multi('Module name', 'modules')
- while ask_yn('Do you want to add a package ?',
+ while ask_yn('Do you want to add a package?',
helptext=_helptext['packages']) == 'y':
self._set_multi('Package name', 'packages')
- while ask_yn('Do you want to add an extra file ?',
+ while ask_yn('Do you want to add an extra file?',
helptext=_helptext['extra_files']) == 'y':
self._set_multi('Extra file/dir name', 'extra_files')
@@ -437,7 +539,7 @@
res = res[:-len('.py')]
return res
- # first pass : packages
+ # first pass: packages
for root, dirs, files in os.walk(curdir):
if to_skip(root):
continue
@@ -455,14 +557,14 @@
if to_skip(root):
continue
- if True in [root.startswith(path) for path in scanned]:
+ if any(root.startswith(path) for path in scanned):
continue
for file in sorted(files):
fullpath = os.path.join(root, file)
if to_skip(fullpath):
continue
- # single module ?
+ # single module?
if os.path.splitext(file)[-1] == '.py':
modules.append(dotted(fullpath))
else:
@@ -475,12 +577,12 @@
existing_values.append(value)
def set_classifier(self):
- self.set_devel_status(self.classifiers)
+ self.set_maturity_status(self.classifiers)
self.set_license(self.classifiers)
self.set_other_classifier(self.classifiers)
def set_other_classifier(self, classifiers):
- if ask_yn('Do you want to set other trove identifiers', 'n',
+ if ask_yn('Do you want to set other trove identifiers?', 'n',
_helptext['trove_generic']) != 'y':
return
self.walk_classifiers(classifiers, [CLASSIFIERS], '')
@@ -497,7 +599,7 @@
classifiers.add(desc[4:] + ' :: ' + key)
continue
- if ask_yn('Do you want to set items under\n "%s" (%d sub-items)'
+ if ask_yn('Do you want to set items under\n "%s" (%d sub-items)?'
% (key, len(trove[key])), 'n',
_helptext['trove_generic']) == 'y':
self.walk_classifiers(classifiers, trovepath + [trove[key]],
@@ -505,7 +607,7 @@
def set_license(self, classifiers):
while True:
- license = ask('What license do you use',
+ license = ask('What license do you use?',
helptext=_helptext['trove_license'], required=False)
if not license:
return
@@ -520,7 +622,7 @@
break
if len(found_list) == 0:
- print('ERROR: Could not find a matching license for "%s"' % \
+ print('ERROR: Could not find a matching license for "%s"' %
license)
continue
@@ -542,115 +644,45 @@
try:
index = found_list[int(choice) - 1]
except ValueError:
- print ("ERROR: Invalid selection, type a number from the list "
- "above.")
+ print("ERROR: Invalid selection, type a number from the list "
+ "above.")
classifiers.add(_CLASSIFIERS_LIST[index])
- return
- def set_devel_status(self, classifiers):
+ def set_maturity_status(self, classifiers):
+ maturity_name = lambda mat: mat.split('- ')[-1]
+ maturity_question = '''\
+ Please select the project status:
+
+ %s
+
+ Status''' % '\n'.join('%s - %s' % (i, maturity_name(n))
+ for i, n in enumerate(PROJECT_MATURITY))
while True:
- choice = ask(dedent('''\
- Please select the project status:
+ choice = ask(dedent(maturity_question), required=False)
- 1 - Planning
- 2 - Pre-Alpha
- 3 - Alpha
- 4 - Beta
- 5 - Production/Stable
- 6 - Mature
- 7 - Inactive
-
- Status'''), required=False)
if choice:
try:
choice = int(choice) - 1
- key = ['Development Status :: 1 - Planning',
- 'Development Status :: 2 - Pre-Alpha',
- 'Development Status :: 3 - Alpha',
- 'Development Status :: 4 - Beta',
- 'Development Status :: 5 - Production/Stable',
- 'Development Status :: 6 - Mature',
- 'Development Status :: 7 - Inactive'][choice]
+ key = PROJECT_MATURITY[choice]
classifiers.add(key)
return
except (IndexError, ValueError):
- print ("ERROR: Invalid selection, type a single digit "
- "number.")
-
- def _dotted_packages(self, data):
- packages = sorted(data)
- modified_pkgs = []
- for pkg in packages:
- pkg = pkg.lstrip('./')
- pkg = pkg.replace('/', '.')
- modified_pkgs.append(pkg)
- return modified_pkgs
-
- def write_setup_script(self):
- if os.path.exists(_FILENAME):
- if os.path.exists('%s.old' % _FILENAME):
- print("ERROR: %(name)s.old backup exists, please check that "
- "current %(name)s is correct and remove %(name)s.old" % \
- {'name': _FILENAME})
- return
- shutil.move(_FILENAME, '%s.old' % _FILENAME)
-
- fp = open(_FILENAME, 'w')
- try:
- fp.write('[metadata]\n')
- # simple string entries
- for name in ('name', 'version', 'summary', 'download_url'):
- fp.write('%s = %s\n' % (name, self.data.get(name, 'UNKNOWN')))
- # optional string entries
- if 'keywords' in self.data and self.data['keywords']:
- fp.write('keywords = %s\n' % ' '.join(self.data['keywords']))
- for name in ('home_page', 'author', 'author_email',
- 'maintainer', 'maintainer_email', 'description-file'):
- if name in self.data and self.data[name]:
- fp.write('%s = %s\n' % (name, self.data[name]))
- if 'description' in self.data:
- fp.write(
- 'description = %s\n'
- % '\n |'.join(self.data['description'].split('\n')))
- # multiple use string entries
- for name in ('platform', 'supported-platform', 'classifier',
- 'requires-dist', 'provides-dist', 'obsoletes-dist',
- 'requires-external'):
- if not(name in self.data and self.data[name]):
- continue
- fp.write('%s = ' % name)
- fp.write(''.join(' %s\n' % val
- for val in self.data[name]).lstrip())
- fp.write('\n[files]\n')
- for name in ('packages', 'modules', 'scripts',
- 'package_data', 'extra_files'):
- if not(name in self.data and self.data[name]):
- continue
- fp.write('%s = %s\n'
- % (name, '\n '.join(self.data[name]).strip()))
- fp.write('\nresources =\n')
- for src, dest in self.data['resources']:
- fp.write(' %s = %s\n' % (src, dest))
- fp.write('\n')
-
- finally:
- fp.close()
-
- os.chmod(_FILENAME, 0644)
- print 'Wrote "%s".' % _FILENAME
+ print("ERROR: Invalid selection, type a single digit "
+ "number.")
def main():
"""Main entry point."""
program = MainProgram()
- # uncomment when implemented
- if not program.load_existing_setup_script():
- program.inspect_directory()
- program.query_user()
- program.update_config_file()
- program.write_setup_script()
+ # # uncomment when implemented
+ # if not program.load_existing_setup_script():
+ # program.inspect_directory()
+ # program.query_user()
+ # program.update_config_file()
+ # program.write_setup_script()
# distutils2.util.cfg_to_args()
+ program()
if __name__ == '__main__':
diff --git a/distutils2/database.py b/distutils2/database.py
new file mode 100644
--- /dev/null
+++ b/distutils2/database.py
@@ -0,0 +1,647 @@
+"""PEP 376 implementation."""
+
+from StringIO import StringIO
+import os
+import re
+import csv
+import sys
+import zipimport
+from hashlib import md5
+from distutils2 import logger
+from distutils2.errors import PackagingError
+from distutils2.version import suggest_normalized_version, VersionPredicate
+from distutils2.metadata import Metadata
+
+
+__all__ = [
+ 'Distribution', 'EggInfoDistribution', 'distinfo_dirname',
+ 'get_distributions', 'get_distribution', 'get_file_users',
+ 'provides_distribution', 'obsoletes_distribution',
+ 'enable_cache', 'disable_cache', 'clear_cache',
+ 'get_file_path', 'get_file']
+
+
+# TODO update docs
+
+DIST_FILES = ('INSTALLER', 'METADATA', 'RECORD', 'REQUESTED', 'RESOURCES')
+
+# Cache
+_cache_name = {} # maps names to Distribution instances
+_cache_name_egg = {} # maps names to EggInfoDistribution instances
+_cache_path = {} # maps paths to Distribution instances
+_cache_path_egg = {} # maps paths to EggInfoDistribution instances
+_cache_generated = False # indicates if .dist-info distributions are cached
+_cache_generated_egg = False # indicates if .dist-info and .egg are cached
+_cache_enabled = True
+
+
+def enable_cache():
+ """
+ Enables the internal cache.
+
+ Note that this function will not clear the cache in any case, for that
+ functionality see :func:`clear_cache`.
+ """
+ global _cache_enabled
+
+ _cache_enabled = True
+
+
+def disable_cache():
+ """
+ Disables the internal cache.
+
+ Note that this function will not clear the cache in any case, for that
+ functionality see :func:`clear_cache`.
+ """
+ global _cache_enabled
+
+ _cache_enabled = False
+
+
+def clear_cache():
+ """ Clears the internal cache. """
+ global _cache_generated, _cache_generated_egg
+
+ _cache_name.clear()
+ _cache_name_egg.clear()
+ _cache_path.clear()
+ _cache_path_egg.clear()
+ _cache_generated = False
+ _cache_generated_egg = False
+
+
+def _yield_distributions(include_dist, include_egg, paths):
+ """
+ Yield .dist-info and .egg(-info) distributions, based on the arguments
+
+ :parameter include_dist: yield .dist-info distributions
+ :parameter include_egg: yield .egg(-info) distributions
+ """
+ for path in paths:
+ realpath = os.path.realpath(path)
+ if not os.path.isdir(realpath):
+ continue
+ for dir in os.listdir(realpath):
+ dist_path = os.path.join(realpath, dir)
+ if include_dist and dir.endswith('.dist-info'):
+ yield Distribution(dist_path)
+ elif include_egg and (dir.endswith('.egg-info') or
+ dir.endswith('.egg')):
+ yield EggInfoDistribution(dist_path)
+
+
+def _generate_cache(use_egg_info, paths):
+ global _cache_generated, _cache_generated_egg
+
+ if _cache_generated_egg or (_cache_generated and not use_egg_info):
+ return
+ else:
+ gen_dist = not _cache_generated
+ gen_egg = use_egg_info
+
+ for dist in _yield_distributions(gen_dist, gen_egg, paths):
+ if isinstance(dist, Distribution):
+ _cache_path[dist.path] = dist
+ if dist.name not in _cache_name:
+ _cache_name[dist.name] = []
+ _cache_name[dist.name].append(dist)
+ else:
+ _cache_path_egg[dist.path] = dist
+ if dist.name not in _cache_name_egg:
+ _cache_name_egg[dist.name] = []
+ _cache_name_egg[dist.name].append(dist)
+
+ if gen_dist:
+ _cache_generated = True
+ if gen_egg:
+ _cache_generated_egg = True
+
+
+class Distribution(object):
+ """Created with the *path* of the ``.dist-info`` directory provided to the
+ constructor. It reads the metadata contained in ``METADATA`` when it is
+ instantiated."""
+
+ name = ''
+ """The name of the distribution."""
+
+ version = ''
+ """The version of the distribution."""
+
+ metadata = None
+ """A :class:`distutils2.metadata.Metadata` instance loaded with
+ the distribution's ``METADATA`` file."""
+
+ 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, path):
+ if _cache_enabled and path in _cache_path:
+ self.metadata = _cache_path[path].metadata
+ else:
+ metadata_path = os.path.join(path, 'METADATA')
+ self.metadata = Metadata(path=metadata_path)
+
+ self.name = self.metadata['Name']
+ self.version = self.metadata['Version']
+ self.path = path
+
+ if _cache_enabled and path not in _cache_path:
+ _cache_path[path] = self
+
+ def __repr__(self):
+ return '<Distribution %r %s at %r>' % (
+ self.name, self.version, self.path)
+
+ def _get_records(self, local=False):
+ results = []
+ with self.get_distinfo_file('RECORD') as record:
+ record_reader = csv.reader(record, delimiter=',',
+ lineterminator='\n')
+ for row in record_reader:
+ missing = [None for i in range(len(row), 3)]
+ path, checksum, size = row + missing
+ if local:
+ path = path.replace('/', os.sep)
+ path = os.path.join(sys.prefix, path)
+ results.append((path, checksum, size))
+ return results
+
+ def get_resource_path(self, relative_path):
+ with self.get_distinfo_file('RESOURCES') as resources_file:
+ resources_reader = csv.reader(resources_file, delimiter=',',
+ lineterminator='\n')
+ 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, local=False):
+ """
+ Iterates over the ``RECORD`` entries and returns a tuple
+ ``(path, md5, size)`` for each line. If *local* is ``True``,
+ the returned path is transformed into a local absolute path.
+ Otherwise the raw value from RECORD is returned.
+
+ A local absolute path is an absolute path in which occurrences of
+ ``'/'`` have been replaced by the system separator given by ``os.sep``.
+
+ :parameter local: flag to say if the path should be returned a local
+ absolute path
+
+ :type local: boolean
+ :returns: iterator of (path, md5, size)
+ """
+ for result in self._get_records(local):
+ yield result
+
+ def uses(self, path):
+ """
+ Returns ``True`` if path is listed in ``RECORD``. *path* can be a local
+ absolute path or a relative ``'/'``-separated path.
+
+ :rtype: boolean
+ """
+ for p, checksum, size in self._get_records():
+ local_absolute = os.path.join(sys.prefix, p)
+ if path == p or path == local_absolute:
+ return True
+ return False
+
+ def get_distinfo_file(self, path, binary=False):
+ """
+ Returns a file located under the ``.dist-info`` directory. Returns a
+ ``file`` instance for the file pointed by *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:`PackagingError` is raised
+ :type path: string
+ :parameter binary: If *binary* is ``True``, opens the file in read-only
+ binary mode (``rb``), otherwise opens it in
+ read-only mode (``r``).
+ :rtype: file object
+ """
+ open_flags = 'r'
+ if binary:
+ open_flags += 'b'
+
+ # 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 PackagingError(
+ '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 PackagingError('invalid path for a dist-info file: %r' %
+ path)
+
+ path = os.path.join(self.path, path)
+ return open(path, open_flags)
+
+ def list_distinfo_files(self, local=False):
+ """
+ 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.
+
+ :parameter local: If *local* is ``True``, each returned path is
+ transformed into a local absolute path. Otherwise the
+ raw value from ``RECORD`` is returned.
+ :type local: boolean
+ :returns: iterator of paths
+ """
+ for path, checksum, size in self._get_records(local):
+ yield path
+
+ def __eq__(self, other):
+ return isinstance(other, Distribution) and self.path == other.path
+
+ # See http://docs.python.org/reference/datamodel#object.__hash__
+ __hash__ = object.__hash__
+
+
+class EggInfoDistribution(object):
+ """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."""
+
+ name = ''
+ """The name of the distribution."""
+
+ version = ''
+ """The version of the distribution."""
+
+ metadata = None
+ """A :class:`distutils2.metadata.Metadata` instance loaded with
+ the distribution's ``METADATA`` file."""
+
+ _REQUIREMENT = re.compile(
+ r'(?P<name>[-A-Za-z0-9_.]+)\s*'
+ r'(?P<first>(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)?\s*'
+ r'(?P<rest>(?:\s*,\s*(?:<|<=|!=|==|>=|>)[-A-Za-z0-9_.]+)*)\s*'
+ r'(?P<extras>\[.*\])?')
+
+ def __init__(self, path):
+ self.path = path
+ if _cache_enabled and path in _cache_path_egg:
+ self.metadata = _cache_path_egg[path].metadata
+ self.name = self.metadata['Name']
+ self.version = self.metadata['Version']
+ return
+
+ # reused from Distribute's pkg_resources
+ def yield_lines(strs):
+ """Yield non-empty/non-comment lines of a ``basestring``
+ or sequence"""
+ if isinstance(strs, basestring):
+ for s in strs.splitlines():
+ s = s.strip()
+ # skip blank lines/comments
+ if s and not s.startswith('#'):
+ yield s
+ else:
+ for ss in strs:
+ for s in yield_lines(ss):
+ yield s
+
+ requires = None
+
+ if path.endswith('.egg'):
+ if os.path.isdir(path):
+ meta_path = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
+ self.metadata = Metadata(path=meta_path)
+ try:
+ req_path = os.path.join(path, 'EGG-INFO', 'requires.txt')
+ with open(req_path, 'r') as fp:
+ requires = fp.read()
+ except IOError:
+ requires = None
+ 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'))
+ self.metadata = Metadata(fileobj=fileobj)
+ try:
+ requires = zipf.get_data('EGG-INFO/requires.txt')
+ except IOError:
+ requires = None
+ self.name = self.metadata['Name']
+ self.version = self.metadata['Version']
+
+ elif path.endswith('.egg-info'):
+ if os.path.isdir(path):
+ path = os.path.join(path, 'PKG-INFO')
+ try:
+ with open(os.path.join(path, 'requires.txt'), 'r') as fp:
+ requires = fp.read()
+ except IOError:
+ requires = None
+ self.metadata = Metadata(path=path)
+ self.name = self.metadata['Name']
+ self.version = self.metadata['Version']
+
+ else:
+ raise ValueError('path must end with .egg-info or .egg, got %r' %
+ path)
+
+ if requires is not None:
+ if self.metadata['Metadata-Version'] == '1.1':
+ # we can't have 1.1 metadata *and* Setuptools requires
+ for field in ('Obsoletes', 'Requires', 'Provides'):
+ del self.metadata[field]
+
+ reqs = []
+
+ if requires is not None:
+ for line in yield_lines(requires):
+ if line.startswith('['):
+ logger.warning(
+ 'extensions in requires.txt are not supported '
+ '(used by %r %s)', self.name, self.version)
+ break
+ else:
+ match = self._REQUIREMENT.match(line.strip())
+ if not match:
+ # this happens when we encounter extras; since they
+ # are written at the end of the file we just exit
+ break
+ else:
+ if match.group('extras'):
+ msg = ('extra requirements are not supported '
+ '(used by %r %s)', self.name, self.version)
+ logger.warning(msg, self.name)
+ name = match.group('name')
+ version = None
+ if match.group('first'):
+ version = match.group('first')
+ if match.group('rest'):
+ version += match.group('rest')
+ version = version.replace(' ', '') # trim spaces
+ if version is None:
+ reqs.append(name)
+ else:
+ reqs.append('%s (%s)' % (name, version))
+
+ if len(reqs) > 0:
+ self.metadata['Requires-Dist'] += reqs
+
+ if _cache_enabled:
+ _cache_path_egg[self.path] = self
+
+ def __repr__(self):
+ return '<EggInfoDistribution %r %s at %r>' % (
+ self.name, self.version, self.path)
+
+ def list_installed_files(self, local=False):
+
+ def _md5(path):
+ with open(path, 'rb') as f:
+ content = f.read()
+ return md5(content).hexdigest()
+
+ def _size(path):
+ return os.stat(path).st_size
+
+ path = self.path
+ if local:
+ path = path.replace('/', os.sep)
+
+ # XXX What about scripts and data files ?
+ if os.path.isfile(path):
+ return [(path, _md5(path), _size(path))]
+ else:
+ files = []
+ for root, dir, files_ in os.walk(path):
+ for item in files_:
+ item = os.path.join(root, item)
+ files.append((item, _md5(item), _size(item)))
+ return files
+
+ return []
+
+ def uses(self, path):
+ return False
+
+ 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__
+
+
+def distinfo_dirname(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"""
+ file_extension = '.dist-info'
+ name = name.replace('-', '_')
+ normalized_version = suggest_normalized_version(version)
+ # Because this is a lookup procedure, something will be returned even if
+ # it is a version that cannot be normalized
+ if normalized_version is None:
+ # Unable to achieve normality?
+ normalized_version = version
+ return '-'.join([name, normalized_version]) + file_extension
+
+
+def get_distributions(use_egg_info=False, paths=None):
+ """
+ Provides an iterator that looks for ``.dist-info`` directories in
+ ``sys.path`` and returns :class:`Distribution` instances for each one of
+ them. If the parameters *use_egg_info* is ``True``, then the ``.egg-info``
+ files and directores are iterated as well.
+
+ :rtype: iterator of :class:`Distribution` and :class:`EggInfoDistribution`
+ instances
+ """
+ if paths is None:
+ paths = sys.path
+
+ if not _cache_enabled:
+ for dist in _yield_distributions(True, use_egg_info, paths):
+ yield dist
+ else:
+ _generate_cache(use_egg_info, paths)
+
+ for dist in _cache_path.values():
+ yield dist
+
+ if use_egg_info:
+ for dist in _cache_path_egg.values():
+ yield dist
+
+
+def get_distribution(name, use_egg_info=False, paths=None):
+ """
+ Scans all elements in ``sys.path`` and looks for all directories
+ ending with ``.dist-info``. Returns a :class:`Distribution`
+ corresponding to the ``.dist-info`` directory that contains the
+ ``METADATA`` that matches *name* for the *name* metadata field.
+ If no distribution exists with the given *name* and the parameter
+ *use_egg_info* is set to ``True``, then all files and directories ending
+ with ``.egg-info`` are scanned. A :class:`EggInfoDistribution` instance is
+ returned if one is found that has metadata that matches *name* for the
+ *name* metadata field.
+
+ This function only returns the first result found, as no more than one
+ value is expected. If the directory is not found, ``None`` is returned.
+
+ :rtype: :class:`Distribution` or :class:`EggInfoDistribution` or None
+ """
+ if paths is None:
+ paths = sys.path
+
+ if not _cache_enabled:
+ for dist in _yield_distributions(True, use_egg_info, paths):
+ if dist.name == name:
+ return dist
+ else:
+ _generate_cache(use_egg_info, paths)
+
+ if name in _cache_name:
+ return _cache_name[name][0]
+ elif use_egg_info and name in _cache_name_egg:
+ return _cache_name_egg[name][0]
+ else:
+ return None
+
+
+def obsoletes_distribution(name, version=None, use_egg_info=False):
+ """
+ Iterates over all distributions to find which distributions obsolete
+ *name*.
+
+ If a *version* is provided, it will be used to filter the results.
+ If the argument *use_egg_info* is set to ``True``, then ``.egg-info``
+ distributions will be considered as well.
+
+ :type name: string
+ :type version: string
+ :parameter name:
+ """
+ for dist in get_distributions(use_egg_info):
+ obsoleted = (dist.metadata['Obsoletes-Dist'] +
+ dist.metadata['Obsoletes'])
+ for obs in obsoleted:
+ o_components = obs.split(' ', 1)
+ if len(o_components) == 1 or version is None:
+ if name == o_components[0]:
+ yield dist
+ break
+ else:
+ try:
+ predicate = VersionPredicate(obs)
+ except ValueError:
+ raise PackagingError(
+ 'distribution %r has ill-formed obsoletes field: '
+ '%r' % (dist.name, obs))
+ if name == o_components[0] and predicate.match(version):
+ yield dist
+ break
+
+
+def provides_distribution(name, version=None, use_egg_info=False):
+ """
+ Iterates over all distributions to find which distributions provide *name*.
+ If a *version* is provided, it will be used to filter the results. Scans
+ all elements in ``sys.path`` and looks for all directories ending with
+ ``.dist-info``. Returns a :class:`Distribution` corresponding to the
+ ``.dist-info`` directory that contains a ``METADATA`` that matches *name*
+ for the name metadata. If the argument *use_egg_info* is set to ``True``,
+ then all files and directories ending with ``.egg-info`` are considered
+ as well and returns an :class:`EggInfoDistribution` instance.
+
+ 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
+ """
+ predicate = None
+ if not version is None:
+ try:
+ predicate = VersionPredicate(name + ' (' + version + ')')
+ except ValueError:
+ raise PackagingError('invalid name or version: %r, %r' %
+ (name, version))
+
+ for dist in get_distributions(use_egg_info):
+ provided = dist.metadata['Provides-Dist'] + dist.metadata['Provides']
+
+ for p in provided:
+ p_components = p.rsplit(' ', 1)
+ if len(p_components) == 1 or predicate is None:
+ if name == p_components[0]:
+ yield dist
+ break
+ else:
+ p_name, p_ver = p_components
+ if len(p_ver) < 2 or p_ver[0] != '(' or p_ver[-1] != ')':
+ raise PackagingError(
+ 'distribution %r has invalid Provides field: %r' %
+ (dist.name, p))
+ p_ver = p_ver[1:-1] # trim off the parenthesis
+ if p_name == name and predicate.match(p_ver):
+ yield dist
+ break
+
+
+def get_file_users(path):
+ """
+ Iterates over all distributions to find out which distributions use
+ *path*.
+
+ :parameter path: can be a local absolute path or a relative
+ ``'/'``-separated path.
+ :type path: string
+ :rtype: iterator of :class:`Distribution` instances
+ """
+ for dist in get_distributions():
+ if dist.uses(path):
+ yield dist
+
+
+def get_file_path(distribution_name, relative_path):
+ """Return the path to a resource file."""
+ dist = get_distribution(distribution_name)
+ if dist is not None:
+ return dist.get_resource_path(relative_path)
+ raise LookupError('no distribution named %r found' % distribution_name)
+
+
+def get_file(distribution_name, relative_path, *args, **kwargs):
+ """Open and return a resource file."""
+ return open(get_file_path(distribution_name, relative_path),
+ *args, **kwargs)
diff --git a/distutils2/depgraph.py b/distutils2/depgraph.py
--- a/distutils2/depgraph.py
+++ b/distutils2/depgraph.py
@@ -1,29 +1,34 @@
-"""Analyse the relationships between the distributions in the system
-and generate a dependency graph.
+"""Class and functions dealing with dependencies between distributions.
+
+This module provides a DependencyGraph class to represent the
+dependencies between distributions. Auxiliary functions can generate a
+graph, find reverse dependencies, and print a graph in DOT format.
"""
+
import sys
+
from StringIO import StringIO
-from distutils2.errors import DistutilsError
+from distutils2.errors import PackagingError
from distutils2.version import VersionPredicate, IrrationalVersionError
__all__ = ['DependencyGraph', 'generate_graph', 'dependent_dists',
'graph_to_dot']
-class DependencyGraph(object):
+class DependencyGraph:
"""
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 labelled with ``label`` (i.e. the version
+ 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
- depencies 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.
+ 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):
@@ -34,40 +39,40 @@
def add_distribution(self, distribution):
"""Add the *distribution* to the graph.
- :type distribution: :class:`pkgutil.Distribution` or
- :class:`pkgutil.EggInfoDistribution`
+ :type distribution: :class:`distutils2.database.Distribution` or
+ :class:`distutils2.database.EggInfoDistribution`
"""
- self.adjacency_list[distribution] = list()
- self.reverse_list[distribution] = list()
- self.missing[distribution] = list()
+ 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:`pkgutil.Distribution` or
- :class:`pkgutil.EggInfoDistribution`
- :type y: :class:`pkgutil.Distribution` or
- :class:`pkgutil.EggInfoDistribution`
+ :type x: :class:`distutils2.database.Distribution` or
+ :class:`distutils2.database.EggInfoDistribution`
+ :type y: :class:`distutils2.database.Distribution` 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 not x in self.reverse_list[y]:
+ 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:`pkgutil.Distribution` or
- :class:`pkgutil.EggInfoDistribution`
+ :type distribution: :class:`distutils2.database.Distribution` or
+ :class:`distutils2.database.EggInfoDistribution`
:type requirement: ``str``
"""
self.missing[distribution].append(requirement)
def _repr_dist(self, dist):
- return '%s %s' % (dist.name, dist.metadata['Version'])
+ return '%r %s' % (dist.name, dist.version)
def repr_node(self, dist, level=1):
"""Prints only a subgraph"""
@@ -77,7 +82,7 @@
dist = self._repr_dist(other)
if label is not None:
dist = '%s [%s]' % (dist, label)
- output.append(' ' * level + '%s' % dist)
+ output.append(' ' * level + str(dist))
suboutput = self.repr_node(other, level + 1)
subs = suboutput.split('\n')
output.extend(subs[1:])
@@ -86,7 +91,7 @@
def __repr__(self):
"""Representation of the graph"""
output = []
- for dist, adjs in self.adjacency_list.iteritems():
+ for dist, adjs in self.adjacency_list.items():
output.append(self.repr_node(dist))
return '\n'.join(output)
@@ -102,46 +107,45 @@
"""
disconnected = []
- f.write("digraph dependencies {\n")
- for dist, adjs in graph.adjacency_list.iteritems():
+ f.write(u"digraph dependencies {\n")
+ for dist, adjs in graph.adjacency_list.items():
if len(adjs) == 0 and not skip_disconnected:
disconnected.append(dist)
- for (other, label) in adjs:
+ for other, label in adjs:
if not label is None:
- f.write('"%s" -> "%s" [label="%s"]\n' %
+ f.write(u'"%s" -> "%s" [label="%s"]\n' %
(dist.name, other.name, label))
else:
- f.write('"%s" -> "%s"\n' % (dist.name, other.name))
+ f.write(u'"%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')
+ f.write(u'subgraph disconnected {\n')
+ f.write(u'label = "Disconnected"\n')
+ f.write(u'bgcolor = red\n')
for dist in disconnected:
- f.write('"%s"' % dist.name)
- f.write('\n')
- f.write('}\n')
- f.write('}\n')
+ f.write(u'"%s"' % dist.name)
+ f.write(u'\n')
+ f.write(u'}\n')
+ f.write(u'}\n')
def generate_graph(dists):
"""Generates a dependency graph from the given distributions.
:parameter dists: a list of distributions
- :type dists: list of :class:`pkgutil.Distribution` and
- :class:`pkgutil.EggInfoDistribution` instances
- :rtype: an :class:`DependencyGraph` instance
+ :type dists: list of :class:`distutils2.database.Distribution` and
+ :class:`distutils2.database.EggInfoDistribution` instances
+ :rtype: a :class:`DependencyGraph` instance
"""
graph = DependencyGraph()
provided = {} # maps names to lists of (version, dist) tuples
- dists = list(dists) # maybe use generator_tools in future
# first, build the graph and find out the provides
for dist in dists:
graph.add_distribution(dist)
provides = (dist.metadata['Provides-Dist'] +
dist.metadata['Provides'] +
- ['%s (%s)' % (dist.name, dist.metadata['Version'])])
+ ['%s (%s)' % (dist.name, dist.version)])
for p in provides:
comps = p.strip().rsplit(" ", 1)
@@ -150,10 +154,10 @@
if len(comps) == 2:
version = comps[1]
if len(version) < 3 or version[0] != '(' or version[-1] != ')':
- raise DistutilsError('Distribution %s has ill formed' \
- 'provides field: %s' % (dist.name, p))
+ raise PackagingError('distribution %r has ill-formed'
+ 'provides field: %r' % (dist.name, p))
version = version[1:-1] # trim off parenthesis
- if not name in provided:
+ if name not in provided:
provided[name] = []
provided[name].append((version, dist))
@@ -170,7 +174,7 @@
name = predicate.name
- if not name in provided:
+ if name not in provided:
graph.add_missing(dist, req)
else:
matched = False
@@ -200,8 +204,9 @@
:param dists: a list of distributions
:param dist: a distribution, member of *dists* for which we are interested
"""
- if not dist in dists:
- raise ValueError('The given distribution is not a member of the list')
+ if dist not in dists:
+ raise ValueError('given distribution %r is not a member of the list' %
+ dist.name)
graph = generate_graph(dists)
dep = [dist] # dependent distributions
@@ -211,7 +216,7 @@
node = fringe.pop()
dep.append(node)
for prev in graph.reverse_list[node]:
- if not prev in dep:
+ if prev not in dep:
fringe.append(prev)
dep.pop(0) # remove dist from dep, was there to prevent infinite loops
@@ -219,7 +224,7 @@
def main():
- from distutils2._backport.pkgutil import get_distributions
+ from distutils2.database import get_distributions
tempout = StringIO()
try:
old = sys.stderr
@@ -229,20 +234,23 @@
graph = generate_graph(dists)
finally:
sys.stderr = old
- except Exception, e:
+ except Exception:
+ e = sys.exc_info()[1]
tempout.seek(0)
tempout = tempout.read()
- print('Could not generate the graph\n%s\n%s\n' % (tempout, str(e)))
+ print(u'Could not generate the graph')
+ print(tempout)
+ print(e)
sys.exit(1)
- for dist, reqs in graph.missing.iteritems():
+ for dist, reqs in graph.missing.items():
if len(reqs) > 0:
- print("Warning: Missing dependencies for %s: %s" % (dist.name,
- ", ".join(reqs)))
+ print(u"Warning: Missing dependencies for %r:" % dist.name,
+ ", ".join(reqs))
# XXX replace with argparse
if len(sys.argv) == 1:
- print('Dependency graph:')
- print(' ' + repr(graph).replace('\n', '\n '))
+ print(u'Dependency graph:')
+ print(u' ', repr(graph).replace(u'\n', u'\n '))
sys.exit(0)
elif len(sys.argv) > 1 and sys.argv[1] in ('-d', '--dot'):
if len(sys.argv) > 2:
@@ -250,15 +258,12 @@
else:
filename = 'depgraph.dot'
- f = open(filename, 'w')
- try:
+ with open(filename, 'w') as f:
graph_to_dot(graph, f, True)
- finally:
- f.close()
tempout.seek(0)
tempout = tempout.read()
print(tempout)
- print('Dot file written at "%s"' % filename)
+ print('Dot file written at %r' % filename)
sys.exit(0)
else:
print('Supported option: -d [filename]')
diff --git a/distutils2/dist.py b/distutils2/dist.py
--- a/distutils2/dist.py
+++ b/distutils2/dist.py
@@ -1,17 +1,11 @@
-"""distutils.dist
-
-Provides the Distribution class, which represents the module distribution
-being built/installed/distributed.
-"""
-
+"""Class representing the distribution being built/installed/etc."""
import os
import re
-import warnings
-import logging
+import sys
-from distutils2.errors import (DistutilsOptionError, DistutilsArgError,
- DistutilsModuleError, DistutilsClassError)
+from distutils2.errors import (PackagingOptionError, PackagingArgError,
+ PackagingModuleError, PackagingClassError)
from distutils2.fancy_getopt import FancyGetopt
from distutils2.util import strtobool, resolve_name
from distutils2 import logger
@@ -19,7 +13,7 @@
from distutils2.config import Config
from distutils2.command import get_command_class, STANDARD_COMMANDS
-# Regex to define acceptable Distutils command names. This is not *quite*
+# Regex to define acceptable Packaging command names. This is not *quite*
# the same as a Python NAME -- I don't allow leading underscores. The fact
# that they're very similar is no coincidence; the default naming scheme is
# to look for a Python module named after the command.
@@ -32,14 +26,16 @@
or: %(script)s cmd --help
"""
+
def gen_usage(script_name):
script = os.path.basename(script_name)
return USAGE % {'script': script}
+
class Distribution(object):
- """The core of the Distutils. Most of the work hiding behind 'setup'
+ """The core of the Packaging. Most of the work hiding behind 'setup'
is really done within a Distribution instance, which farms the work out
- to the Distutils commands specified on the command line.
+ to the Packaging commands specified on the command line.
Setup scripts will almost never instantiate Distribution directly,
unless the 'setup()' function is totally inadequate to their needs.
@@ -52,33 +48,31 @@
# 'global_options' describes the command-line options that may be
# supplied to the setup script prior to any actual commands.
- # Eg. "./setup.py -n" or "./setup.py --quiet" both take advantage of
+ # Eg. "pysetup -n" or "pysetup --dry-run" both take advantage of
# these global options. This list should be kept to a bare minimum,
# since every global option is also valid as a command option -- and we
# don't want to pollute the commands with too many options that they
# have minimal control over.
- # The fourth entry for verbose means that it can be repeated.
- global_options = [('verbose', 'v', "run verbosely (default)", 1),
- ('quiet', 'q', "run quietly (turns verbosity off)"),
- ('dry-run', 'n', "don't actually do anything"),
- ('help', 'h', "show detailed help message"),
- ('no-user-cfg', None,
- 'ignore pydistutils.cfg in your home directory'),
+ global_options = [
+ ('dry-run', 'n', "don't actually do anything"),
+ ('help', 'h', "show detailed help message"),
+ ('no-user-cfg', None, 'ignore pydistutils.cfg in your home directory'),
]
# 'common_usage' is a short (2-3 line) string describing the common
# usage of the setup script.
- common_usage = """\
+ common_usage = u"""\
Common commands: (see '--help-commands' for more)
- setup.py build will build the package underneath 'build/'
- setup.py install will install the package
+ pysetup run build will build the package underneath 'build/'
+ pysetup run install will install the package
"""
# options that are not propagated to the commands
display_options = [
('help-commands', None,
"list all available commands"),
+ # XXX this is obsoleted by the pysetup metadata action
('name', None,
"print package name"),
('version', 'V',
@@ -127,7 +121,7 @@
display_option_names = [x[0].replace('-', '_') for x in display_options]
# negative options are options that exclude other options
- negative_opt = {'quiet': 'verbose'}
+ negative_opt = {}
# -- Creation/initialization methods -------------------------------
def __init__(self, attrs=None):
@@ -142,11 +136,10 @@
"""
# Default values for our command-line options
- self.verbose = 1
- self.dry_run = 0
- self.help = 0
+ self.dry_run = False
+ self.help = False
for attr in self.display_option_names:
- setattr(self, attr, 0)
+ setattr(self, attr, False)
# Store the configuration
self.config = Config(self)
@@ -237,21 +230,21 @@
options = attrs.get('options')
if options is not None:
del attrs['options']
- for (command, cmd_options) in options.iteritems():
+ for command, cmd_options in options.items():
opt_dict = self.get_option_dict(command)
- for (opt, val) in cmd_options.iteritems():
+ for opt, val in cmd_options.items():
opt_dict[opt] = ("setup script", val)
# Now work on the rest of the attributes. Any attribute that's
# not already defined is invalid!
- for key, val in attrs.iteritems():
+ for key, val in attrs.items():
if self.metadata.is_metadata_field(key):
self.metadata[key] = val
elif hasattr(self, key):
setattr(self, key, val)
else:
- msg = "Unknown distribution option: %r" % key
- warnings.warn(msg)
+ logger.warning(
+ 'unknown argument given to Distribution: %r', key)
# no-user-cfg is handled before other command line args
# because other args override the config files, and this
@@ -292,24 +285,23 @@
commands = sorted(self.command_options)
if header is not None:
- self.announce(indent + header)
+ logger.info(indent + header)
indent = indent + " "
if not commands:
- self.announce(indent + "no commands known yet")
+ logger.info(indent + "no commands known yet")
return
for cmd_name in commands:
opt_dict = self.command_options.get(cmd_name)
if opt_dict is None:
- self.announce(indent +
- "no option dict for %r command" % cmd_name)
+ logger.info(indent + "no option dict for %r command",
+ cmd_name)
else:
- self.announce(indent +
- "option dict for %r command:" % cmd_name)
+ logger.info(indent + "option dict for %r command:", cmd_name)
out = pformat(opt_dict)
for line in out.split('\n'):
- self.announce(indent + " " + line)
+ logger.info(indent + " " + line)
# -- Config file finding/parsing methods ---------------------------
# XXX to be removed
@@ -326,15 +318,15 @@
'script_args' instance attribute (which defaults to 'sys.argv[1:]'
-- see 'setup()' in run.py). This list is first processed for
"global options" -- options that set attributes of the Distribution
- instance. Then, it is alternately scanned for Distutils commands
+ instance. Then, it is alternately scanned for Packaging commands
and options for that command. Each new command terminates the
options for the previous command. The allowed options for a
command are determined by the 'user_options' attribute of the
command class -- thus, we have to be able to load command classes
in order to parse the command line. Any error in that 'options'
- attribute raises DistutilsGetoptError; any error on the
- command line raises DistutilsArgError. If no Distutils commands
- were found on the command line, raises DistutilsArgError. Return
+ attribute raises PackagingGetoptError; any error on the
+ command line raises PackagingArgError. If no Packaging commands
+ were found on the command line, raises PackagingArgError. Return
true if command line was successfully parsed and we should carry
on with executing commands; false if no errors but we shouldn't
execute commands (currently, this only happens if user asks for
@@ -360,14 +352,6 @@
args = parser.getopt(args=self.script_args, object=self)
option_order = parser.get_option_order()
- handler = logging.StreamHandler()
- logger.addHandler(handler)
-
- if self.verbose:
- handler.setLevel(logging.DEBUG)
- else:
- handler.setLevel(logging.INFO)
-
# for display options we return immediately
if self.handle_display_options(option_order):
return
@@ -378,8 +362,8 @@
return
# Handle the cases of --help as a "global" option, ie.
- # "setup.py --help" and "setup.py --help command ...". For the
- # former, we show global options (--verbose, --dry-run, etc.)
+ # "pysetup run --help" and "pysetup run --help command ...". For the
+ # former, we show global options (--dry-run, etc.)
# and display-only options (--name, --version, etc.); for the
# latter, we omit the display-only options and show help for
# each command listed on the command line.
@@ -419,24 +403,24 @@
# it takes.
try:
cmd_class = get_command_class(command)
- except DistutilsModuleError, msg:
- raise DistutilsArgError(msg)
+ except PackagingModuleError:
+ raise PackagingArgError(sys.exc_info()[1])
- # XXX We want to push this in distutils.command
+ # XXX We want to push this in distutils2.command
#
# Require that the command class be derived from Command -- want
# to be sure that the basic "command" interface is implemented.
for meth in ('initialize_options', 'finalize_options', 'run'):
if hasattr(cmd_class, meth):
continue
- raise DistutilsClassError(
+ raise PackagingClassError(
'command %r must implement %r' % (cmd_class, meth))
# Also make sure that the command object provides a list of its
# known options.
if not (hasattr(cmd_class, 'user_options') and
isinstance(cmd_class.user_options, list)):
- raise DistutilsClassError(
+ raise PackagingClassError(
"command class %s must provide "
"'user_options' attribute (a list of tuples)" % cmd_class)
@@ -451,7 +435,7 @@
# format (tuple of four) so we need to preprocess them here.
if (hasattr(cmd_class, 'help_options') and
isinstance(cmd_class.help_options, list)):
- help_options = fix_help_options(cmd_class.help_options)
+ help_options = cmd_class.help_options[:]
else:
help_options = []
@@ -461,21 +445,22 @@
cmd_class.user_options +
help_options)
parser.set_negative_aliases(negative_opt)
- (args, opts) = parser.getopt(args[1:])
+ args, opts = parser.getopt(args[1:])
if hasattr(opts, 'help') and opts.help:
- self._show_help(parser, display_options=0, commands=[cmd_class])
+ self._show_help(parser, display_options=False,
+ commands=[cmd_class])
return
if (hasattr(cmd_class, 'help_options') and
isinstance(cmd_class.help_options, list)):
- help_option_found = 0
- for (help_option, short, desc, func) in cmd_class.help_options:
+ help_option_found = False
+ for help_option, short, desc, func in cmd_class.help_options:
if hasattr(opts, help_option.replace('-', '_')):
- help_option_found = 1
+ help_option_found = True
if hasattr(func, '__call__'):
func()
else:
- raise DistutilsClassError(
+ raise PackagingClassError(
"invalid help function %r for help option %r: "
"must be a callable object (function, etc.)"
% (func, help_option))
@@ -486,7 +471,7 @@
# Put the options from the command line into their official
# holding pen, the 'command_options' dictionary.
opt_dict = self.get_option_dict(command)
- for (name, value) in vars(opts).iteritems():
+ for name, value in vars(opts).items():
opt_dict[name] = ("command line", value)
return args
@@ -502,7 +487,7 @@
else:
self.convert_2to3_doctests = []
- def _show_help(self, parser, global_options=1, display_options=1,
+ def _show_help(self, parser, global_options=True, display_options=True,
commands=[]):
"""Show help for the setup script command line in the form of
several lists of command-line options. 'parser' should be a
@@ -511,7 +496,7 @@
generate the correct help text.
If 'global_options' is true, lists the global options:
- --verbose, --dry-run, etc. If 'display_options' is true, lists
+ --dry-run, etc. If 'display_options' is true, lists
the "display-only" options: --name, --version, etc. Finally,
lists per-command help for every command name or command class
in 'commands'.
@@ -526,14 +511,14 @@
options = self.global_options
parser.set_option_table(options)
parser.print_help(self.common_usage + "\nGlobal options:")
- print('')
+ print(u'')
if display_options:
parser.set_option_table(self.display_options)
parser.print_help(
"Information display options (just display " +
"information, ignore any commands)")
- print('')
+ print(u'')
for command in self.commands:
if isinstance(command, type) and issubclass(command, Command):
@@ -542,12 +527,11 @@
cls = get_command_class(command)
if (hasattr(cls, 'help_options') and
isinstance(cls.help_options, list)):
- parser.set_option_table(cls.user_options +
- fix_help_options(cls.help_options))
+ parser.set_option_table(cls.user_options + cls.help_options)
else:
parser.set_option_table(cls.user_options)
parser.print_help("Options for %r command:" % cls.__name__)
- print('')
+ print(u'')
print(gen_usage(self.script_name))
@@ -562,30 +546,30 @@
# we ignore "foo bar").
if self.help_commands:
self.print_commands()
- print('')
+ print()
print(gen_usage(self.script_name))
return 1
# If user supplied any of the "display metadata" options, then
# display that metadata in the order in which the user supplied the
# metadata options.
- any_display_options = 0
- is_display_option = {}
+ any_display_options = False
+ is_display_option = set()
for option in self.display_options:
- is_display_option[option[0]] = 1
+ is_display_option.add(option[0])
for opt, val in option_order:
- if val and is_display_option.get(opt):
+ if val and opt in is_display_option:
opt = opt.replace('-', '_')
value = self.metadata[opt]
- if opt in ['keywords', 'platform']:
+ if opt in ('keywords', 'platform'):
print(','.join(value))
elif opt in ('classifier', 'provides', 'requires',
'obsoletes'):
print('\n'.join(value))
else:
print(value)
- any_display_options = 1
+ any_display_options = True
return any_display_options
@@ -630,14 +614,14 @@
"Standard commands",
max_length)
if extra_commands:
- print
+ print()
self.print_command_list(extra_commands,
"Extra commands",
max_length)
# -- Command class/object methods ----------------------------------
- def get_command_obj(self, command, create=1):
+ def get_command_obj(self, command, create=True):
"""Return the command object for 'command'. Normally this object
is cached on a previous call to 'get_command_obj()'; if no command
object for 'command' is in the cache, then we either create and
@@ -660,7 +644,6 @@
options = self.command_options.get(command)
if options:
self._set_command_options(cmd_obj, options)
-
return cmd_obj
def _set_command_options(self, command_obj, option_dict=None):
@@ -678,7 +661,7 @@
logger.debug(" setting options for %r command:", command_name)
- for (option, (source, value)) in option_dict.iteritems():
+ for option, (source, value) in option_dict.items():
logger.debug(" %s = %s (from %s)", option, value, source)
try:
bool_opts = [x.replace('-', '_')
@@ -691,7 +674,7 @@
neg_opt = {}
try:
- is_string = isinstance(value, str)
+ is_string = isinstance(value, basestring)
if option in neg_opt and is_string:
setattr(command_obj, neg_opt[option], not strtobool(value))
elif option in bool_opts and is_string:
@@ -699,13 +682,13 @@
elif hasattr(command_obj, option):
setattr(command_obj, option, value)
else:
- raise DistutilsOptionError(
+ raise PackagingOptionError(
"error in %s: command %r has no such option %r" %
(source, command_name, option))
- except ValueError, msg:
- raise DistutilsOptionError(msg)
+ except ValueError:
+ raise PackagingOptionError(sys.exc_info()[1])
- def get_reinitialized_command(self, command, reinit_subcommands=0):
+ def get_reinitialized_command(self, command, reinit_subcommands=False):
"""Reinitializes a command to the state it was in when first
returned by 'get_command_obj()': ie., initialized but not yet
finalized. This provides the opportunity to sneak option
@@ -734,8 +717,8 @@
if not command.finalized:
return command
command.initialize_options()
- command.finalized = 0
self.have_run[command_name] = 0
+ command.finalized = False
self._set_command_options(command)
if reinit_subcommands:
@@ -746,9 +729,6 @@
# -- Methods that operate on the Distribution ----------------------
- def announce(self, msg, level=logging.INFO):
- logger.log(level, msg)
-
def run_commands(self):
"""Run each command that was seen on the setup script command line.
Uses the list of commands found and cache of command objects
@@ -796,17 +776,17 @@
if hooks is None:
return
- for hook in hooks.itervalues():
+ for hook in hooks.values():
if isinstance(hook, basestring):
try:
hook_obj = resolve_name(hook)
- except ImportError, e:
- raise DistutilsModuleError(e)
+ except ImportError:
+ raise PackagingModuleError(sys.exc_info()[1])
else:
hook_obj = hook
if not hasattr(hook_obj, '__call__'):
- raise DistutilsOptionError('hook %r is not callable' % hook)
+ raise PackagingOptionError('hook %r is not callable' % hook)
logger.info('running %s %s for command %s',
hook_kind, hook, cmd_obj.get_command_name())
@@ -838,15 +818,3 @@
return (self.has_pure_modules() and
not self.has_ext_modules() and
not self.has_c_libraries())
-
-
-# XXX keep for compat or remove?
-
-def fix_help_options(options):
- """Convert a 4-tuple 'help_options' list as found in various command
- classes to the 3-tuple form required by FancyGetopt.
- """
- new_options = []
- for help_tuple in options:
- new_options.append(help_tuple[0:3])
- return new_options
diff --git a/distutils2/errors.py b/distutils2/errors.py
--- a/distutils2/errors.py
+++ b/distutils2/errors.py
@@ -1,84 +1,82 @@
-"""distutils.errors
+"""Exceptions used throughout the package.
-Provides exceptions used by the Distutils modules. Note that Distutils
-modules may raise standard exceptions; in particular, SystemExit is
-usually raised for errors that are obviously the end-user's fault
-(eg. bad command-line arguments).
+Submodules of distutils2 may raise exceptions defined in this module as
+well as standard exceptions; in particular, SystemExit is usually raised
+for errors that are obviously the end-user's fault (e.g. bad
+command-line arguments).
+"""
-This module is safe to use in "from ... import *" mode; it only exports
-symbols whose names start with "Distutils" and end with "Error"."""
+class PackagingError(Exception):
+ """The root of all Packaging evil."""
-class DistutilsError(Exception):
- """The root of all Distutils evil."""
-
-class DistutilsModuleError(DistutilsError):
+class PackagingModuleError(PackagingError):
"""Unable to load an expected module, or to find an expected class
within some module (in particular, command modules and classes)."""
-class DistutilsClassError(DistutilsError):
+class PackagingClassError(PackagingError):
"""Some command class (or possibly distribution class, if anyone
feels a need to subclass Distribution) is found not to be holding
up its end of the bargain, ie. implementing some part of the
"command "interface."""
-class DistutilsGetoptError(DistutilsError):
+class PackagingGetoptError(PackagingError):
"""The option table provided to 'fancy_getopt()' is bogus."""
-class DistutilsArgError(DistutilsError):
+class PackagingArgError(PackagingError):
"""Raised by fancy_getopt in response to getopt.error -- ie. an
error in the command line usage."""
-class DistutilsFileError(DistutilsError):
+class PackagingFileError(PackagingError):
"""Any problems in the filesystem: expected file not found, etc.
Typically this is for problems that we detect before IOError or
OSError could be raised."""
-class DistutilsOptionError(DistutilsError):
+class PackagingOptionError(PackagingError):
"""Syntactic/semantic errors in command options, such as use of
mutually conflicting options, or inconsistent options,
badly-spelled values, etc. No distinction is made between option
values originating in the setup script, the command line, config
files, or what-have-you -- but if we *know* something originated in
- the setup script, we'll raise DistutilsSetupError instead."""
+ the setup script, we'll raise PackagingSetupError instead."""
-class DistutilsSetupError(DistutilsError):
+class PackagingSetupError(PackagingError):
"""For errors that can be definitely blamed on the setup script,
such as invalid keyword arguments to 'setup()'."""
-class DistutilsPlatformError(DistutilsError):
+class PackagingPlatformError(PackagingError):
"""We don't know how to do something on the current platform (but
we do know how to do it on some platform) -- eg. trying to compile
C files on a platform not supported by a CCompiler subclass."""
-class DistutilsExecError(DistutilsError):
+class PackagingExecError(PackagingError):
"""Any problems executing an external program (such as the C
compiler, when compiling C files)."""
-class DistutilsInternalError(DistutilsError):
+class PackagingInternalError(PackagingError):
"""Internal inconsistencies or impossibilities (obviously, this
should never be seen if the code is working!)."""
-class DistutilsTemplateError(DistutilsError):
+class PackagingTemplateError(PackagingError):
"""Syntax error in a file list template."""
-class DistutilsByteCompileError(DistutilsError):
+class PackagingByteCompileError(PackagingError):
"""Byte compile error."""
-class DistutilsIndexError(DistutilsError):
+class PackagingPyPIError(PackagingError):
"""Any problem occuring during using the indexes."""
@@ -109,15 +107,15 @@
"""Attempt to process an unknown file type."""
-class MetadataMissingError(DistutilsError):
+class MetadataMissingError(PackagingError):
"""A required metadata is missing"""
-class MetadataConflictError(DistutilsError):
+class MetadataConflictError(PackagingError):
"""Attempt to read or write metadata fields that are conflictual."""
-class MetadataUnrecognizedVersionError(DistutilsError):
+class MetadataUnrecognizedVersionError(PackagingError):
"""Unknown metadata version number."""
diff --git a/distutils2/fancy_getopt.py b/distutils2/fancy_getopt.py
--- a/distutils2/fancy_getopt.py
+++ b/distutils2/fancy_getopt.py
@@ -1,22 +1,23 @@
-"""distutils.fancy_getopt
+"""Command line parsing machinery.
-Wrapper around the standard getopt module that provides the following
-additional features:
+The FancyGetopt class is a Wrapper around the getopt module that
+provides the following additional features:
* short and long options are tied together
* options have help strings, so fancy_getopt could potentially
create a complete usage summary
- * options set attributes of a passed-in object
+ * options set attributes of a passed-in object.
+
+It is used under the hood by the command classes. Do not use directly.
"""
+import getopt
+import re
+import sys
+import textwrap
-import sys
-import string
-import re
-import getopt
-import textwrap
-from distutils2.errors import DistutilsGetoptError, DistutilsArgError
+from distutils2.errors import PackagingGetoptError, PackagingArgError
-# Much like command_re in distutils.core, this is close to but not quite
+# Much like command_re in distutils2.core, this is close to but not quite
# the same as a Python NAME -- except, in the spirit of most GNU
# utilities, we use '-' in place of '_'. (The spirit of LISP lives on!)
# The similarities to NAME are again not a coincidence...
@@ -38,6 +39,7 @@
--quiet is the "negative alias" of --verbose, then "--quiet"
on the command line sets 'verbose' to false
"""
+
def __init__(self, option_table=None):
# The option table is (currently) a list of tuples. The
@@ -90,7 +92,7 @@
def add_option(self, long_option, short_option=None, help_string=None):
if long_option in self.option_index:
- raise DistutilsGetoptError(
+ raise PackagingGetoptError(
"option conflict: already an option '%s'" % long_option)
else:
option = (long_option, short_option, help_string)
@@ -104,13 +106,13 @@
def _check_alias_dict(self, aliases, what):
assert isinstance(aliases, dict)
- for (alias, opt) in aliases.iteritems():
+ for alias, opt in aliases.items():
if alias not in self.option_index:
- raise DistutilsGetoptError(
+ raise PackagingGetoptError(
("invalid %s '%s': "
"option '%s' not defined") % (what, alias, alias))
if opt not in self.option_index:
- raise DistutilsGetoptError(
+ raise PackagingGetoptError(
("invalid %s '%s': "
"aliased option '%s' not defined") % (what, alias, opt))
@@ -139,76 +141,76 @@
for option in self.option_table:
if len(option) == 3:
- long, short, help = option
+ longopt, short, help = option
repeat = 0
elif len(option) == 4:
- long, short, help, repeat = option
+ longopt, short, help, repeat = option
else:
# the option table is part of the code, so simply
# assert that it is correct
raise ValueError("invalid option tuple: %r" % option)
# Type- and value-check the option names
- if not isinstance(long, str) or len(long) < 2:
- raise DistutilsGetoptError(
+ if not isinstance(longopt, basestring) or len(longopt) < 2:
+ raise PackagingGetoptError(
("invalid long option '%s': "
- "must be a string of length >= 2") % long)
+ "must be a string of length >= 2") % longopt)
if (not ((short is None) or
- (isinstance(short, str) and len(short) == 1))):
- raise DistutilsGetoptError(
+ (isinstance(short, basestring) and len(short) == 1))):
+ raise PackagingGetoptError(
("invalid short option '%s': "
- "must a single character or None") % short)
+ "must be a single character or None") % short)
- self.repeat[long] = repeat
- self.long_opts.append(long)
+ self.repeat[longopt] = repeat
+ self.long_opts.append(longopt)
- if long[-1] == '=': # option takes an argument?
+ if longopt[-1] == '=': # option takes an argument?
if short:
short = short + ':'
- long = long[0:-1]
- self.takes_arg[long] = 1
+ longopt = longopt[0:-1]
+ self.takes_arg[longopt] = 1
else:
# Is option is a "negative alias" for some other option (eg.
# "quiet" == "!verbose")?
- alias_to = self.negative_alias.get(long)
+ alias_to = self.negative_alias.get(longopt)
if alias_to is not None:
if self.takes_arg[alias_to]:
- raise DistutilsGetoptError(
+ raise PackagingGetoptError(
("invalid negative alias '%s': "
"aliased option '%s' takes a value") % \
- (long, alias_to))
+ (longopt, alias_to))
- self.long_opts[-1] = long # XXX redundant?!
- self.takes_arg[long] = 0
+ self.long_opts[-1] = longopt # XXX redundant?!
+ self.takes_arg[longopt] = 0
else:
- self.takes_arg[long] = 0
+ self.takes_arg[longopt] = 0
# If this is an alias option, make sure its "takes arg" flag is
# the same as the option it's aliased to.
- alias_to = self.alias.get(long)
+ alias_to = self.alias.get(longopt)
if alias_to is not None:
- if self.takes_arg[long] != self.takes_arg[alias_to]:
- raise DistutilsGetoptError(
+ if self.takes_arg[longopt] != self.takes_arg[alias_to]:
+ raise PackagingGetoptError(
("invalid alias '%s': inconsistent with "
"aliased option '%s' (one of them takes a value, "
- "the other doesn't") % (long, alias_to))
+ "the other doesn't") % (longopt, alias_to))
# Now enforce some bondage on the long option name, so we can
# later translate it to an attribute name on some object. Have
# to do this a bit late to make sure we've removed any trailing
# '='.
- if not longopt_re.match(long):
- raise DistutilsGetoptError(
+ if not longopt_re.match(longopt):
+ raise PackagingGetoptError(
("invalid long option name '%s' " +
- "(must be letters, numbers, hyphens only") % long)
+ "(must be letters, numbers, hyphens only") % longopt)
- self.attr_name[long] = long.replace('-', '_')
+ self.attr_name[longopt] = longopt.replace('-', '_')
if short:
self.short_opts.append(short)
- self.short2long[short[0]] = long
+ self.short2long[short[0]] = longopt
def getopt(self, args=None, object=None):
"""Parse command-line options in args. Store as attributes on object.
@@ -231,11 +233,12 @@
self._grok_option_table()
- short_opts = string.join(self.short_opts)
+ short_opts = ' '.join(self.short_opts)
+
try:
opts, args = getopt.getopt(args, short_opts, self.long_opts)
- except getopt.error, msg:
- raise DistutilsArgError(msg)
+ except getopt.error:
+ raise PackagingArgError(sys.exc_info()[1])
for opt, val in opts:
if len(opt) == 2 and opt[0] == '-': # it's a short option
@@ -278,6 +281,8 @@
"""
if self.option_order is None:
raise RuntimeError("'getopt()' hasn't been called yet")
+ else:
+ return self.option_order
return self.option_order
@@ -291,10 +296,10 @@
# First pass: determine maximum length of long option names
max_opt = 0
for option in self.option_table:
- long = option[0]
+ longopt = option[0]
short = option[1]
- l = len(long)
- if long[-1] == '=':
+ l = len(longopt)
+ if longopt[-1] == '=':
l = l - 1
if short is not None:
l = l + 5 # " (-x)" where short == 'x'
@@ -334,22 +339,20 @@
lines = ['Option summary:']
for option in self.option_table:
- long, short, help = option[:3]
+ longopt, short, help = option[:3]
text = textwrap.wrap(help, text_width)
- if long[-1] == '=':
- long = long[0:-1]
# Case 1: no short option at all (makes life easy)
if short is None:
if text:
- lines.append(" --%-*s %s" % (max_opt, long, text[0]))
+ lines.append(" --%-*s %s" % (max_opt, longopt, text[0]))
else:
- lines.append(" --%-*s " % (max_opt, long))
+ lines.append(" --%-*s " % (max_opt, longopt))
# Case 2: we have a short option, so we have to include it
# just after the long option
else:
- opt_names = "%s (-%s)" % (long, short)
+ opt_names = "%s (-%s)" % (longopt, short)
if text:
lines.append(" --%-*s %s" %
(max_opt, opt_names, text[0]))
@@ -365,7 +368,7 @@
if file is None:
file = sys.stdout
for line in self.generate_help(header):
- file.write(line + "\n")
+ file.write(line + u"\n")
def fancy_getopt(options, negative_opt, object, args):
@@ -374,71 +377,6 @@
return parser.getopt(args, object)
-if 'maketrans' in str.__dict__ :
- # Python 3.2+
- WS_TRANS = str.maketrans(string.whitespace, ' ' * len(string.whitespace))
-else :
- # Depreciated syntax
- WS_TRANS = string.maketrans(string.whitespace, ' ' * len(string.whitespace))
-
-
-def wrap_text(text, width):
- """wrap_text(text : string, width : int) -> [string]
-
- Split 'text' into multiple lines of no more than 'width' characters
- each, and return the list of strings that results.
- """
-
- if text is None:
- return []
- if len(text) <= width:
- return [text]
-
- text = string.expandtabs(text)
- text = string.translate(text, WS_TRANS)
- chunks = re.split(r'( +|-+)', text)
- chunks = filter(None, chunks) # ' - ' results in empty strings
- lines = []
-
- while chunks:
-
- cur_line = [] # list of chunks (to-be-joined)
- cur_len = 0 # length of current line
-
- while chunks:
- l = len(chunks[0])
- if cur_len + l <= width: # can squeeze (at least) this chunk in
- cur_line.append(chunks[0])
- del chunks[0]
- cur_len = cur_len + l
- else: # this line is full
- # drop last chunk if all space
- if cur_line and cur_line[-1][0] == ' ':
- del cur_line[-1]
- break
-
- if chunks: # any chunks left to process?
-
- # if the current line is still empty, then we had a single
- # chunk that's too big too fit on a line -- so we break
- # down and break it up at the line width
- if cur_len == 0:
- cur_line.append(chunks[0][0:width])
- chunks[0] = chunks[0][width:]
-
- # all-whitespace chunks at the end of a line can be discarded
- # (and we know from the re.split above that if a chunk has
- # *any* whitespace, it is *all* whitespace)
- if chunks[0][0] == ' ':
- del chunks[0]
-
- # and store this line in the list-of-all-lines -- as a single
- # string, of course!
- lines.append(string.join(cur_line, ''))
-
- return lines
-
-
class OptionDummy(object):
"""Dummy class just used as a place to hold command-line option
values as instance attributes."""
diff --git a/distutils2/install.py b/distutils2/install.py
--- a/distutils2/install.py
+++ b/distutils2/install.py
@@ -1,34 +1,37 @@
-"""Provides installations scripts.
+"""Building blocks for installers.
-The goal of this script is to install a release from the indexes (eg.
-PyPI), including the dependencies of the releases if needed.
+When used as a script, this module installs a release thanks to info
+obtained from an index (e.g. PyPI), with dependencies.
-It uses the work made in pkgutil and by the index crawlers to browse the
-installed distributions, and rely on the instalation commands to install.
+This is a higher-level module built on distutils2.database and
+distutils2.pypi.
"""
-import shutil
import os
import sys
import stat
import errno
-import itertools
+import shutil
import logging
import tempfile
+from sysconfig import get_config_var, get_path, is_python_build
from distutils2 import logger
-from distutils2._backport.pkgutil import get_distributions
-from distutils2._backport.pkgutil import get_distribution
-from distutils2._backport.sysconfig import get_config_var
+from distutils2.dist import Distribution
+from distutils2.util import (_is_archive_file, ask, get_install_method,
+ egginfo_to_distinfo, unpack_archive)
+from distutils2.pypi import wrapper
+from distutils2.version import get_version_predicate
+from distutils2.database import get_distributions, get_distribution
from distutils2.depgraph import generate_graph
-from distutils2.index import wrapper
-from distutils2.index.errors import ProjectNotFound, ReleaseNotFound
-from distutils2.errors import (DistutilsError, InstallationException,
- InstallationConflict)
-from distutils2.version import get_version_predicate
+
+from distutils2.errors import (PackagingError, InstallationException,
+ InstallationConflict, CCompilerError)
+from distutils2.pypi.errors import ProjectNotFound, ReleaseNotFound
+from distutils2 import database
__all__ = ['install_dists', 'install_from_infos', 'get_infos', 'remove',
- 'install']
+ 'install', 'install_local_project']
def _move_files(files, destination):
@@ -40,42 +43,53 @@
:param files: a list of files to move.
:param destination: the destination directory to put on the files.
"""
+
for old in files:
- # not using os.path.join() because basename() might not be
- # unique in destination
- new = "%s%s" % (destination, old)
-
+ filename = os.path.split(old)[-1]
+ new = os.path.join(destination, filename)
# try to make the paths.
try:
os.makedirs(os.path.dirname(new))
- except OSError, e:
- if e.errno == errno.EEXIST:
- pass
- else:
- raise e
+ except OSError:
+ e = sys.exc_info()[1]
+ if e.errno != errno.EEXIST:
+ raise
os.rename(old, new)
yield old, new
-def _run_d1_install(archive_dir, path):
+def _run_distutils_install(path):
# backward compat: using setuptools or plain-distutils
- cmd = '%s setup.py install --root=%s --record=%s'
- setup_py = os.path.join(archive_dir, 'setup.py')
- if 'setuptools' in open(setup_py).read():
- cmd += ' --single-version-externally-managed'
-
- # how to place this file in the egg-info dir
- # for non-distutils2 projects ?
- record_file = os.path.join(archive_dir, 'RECORD')
- os.system(cmd % (sys.executable, path, record_file))
+ cmd = '%s setup.py install --record=%s'
+ record_file = os.path.join(path, 'RECORD')
+ os.system(cmd % (sys.executable, record_file))
if not os.path.exists(record_file):
raise ValueError('failed to install')
- return open(record_file).read().split('\n')
+ else:
+ egginfo_to_distinfo(record_file, remove_egginfo=True)
-def _run_d2_install(archive_dir, path):
- # using our own install command
- raise NotImplementedError()
+def _run_setuptools_install(path):
+ cmd = '%s setup.py install --record=%s --single-version-externally-managed'
+ record_file = os.path.join(path, 'RECORD')
+
+ os.system(cmd % (sys.executable, record_file))
+ if not os.path.exists(record_file):
+ raise ValueError('failed to install')
+ else:
+ egginfo_to_distinfo(record_file, remove_egginfo=True)
+
+
+def _run_packaging_install(path):
+ # XXX check for a valid setup.cfg?
+ dist = Distribution()
+ dist.parse_config_files()
+ try:
+ dist.run_command('install_dist')
+ name = dist.metadata['Name']
+ return database.get_distribution(name) is not None
+ except (IOError, os.error, PackagingError, CCompilerError):
+ raise ValueError("Failed to install, " + str(sys.exc_info()[1]))
def _install_dist(dist, path):
@@ -87,64 +101,105 @@
* copy the files in "path"
* determine if the distribution is distutils2 or distutils1.
"""
- where = dist.unpack(path)
+ where = dist.unpack()
- # get into the dir
- archive_dir = None
- for item in os.listdir(where):
- fullpath = os.path.join(where, item)
- if os.path.isdir(fullpath):
- archive_dir = fullpath
- break
-
- if archive_dir is None:
+ if where is None:
raise ValueError('Cannot locate the unpacked archive')
- # install
+ return _run_install_from_archive(where)
+
+
+def install_local_project(path):
+ """Install a distribution from a source directory.
+
+ If the source directory contains a setup.py install using distutils1.
+ If a setup.cfg is found, install using the install_dist command.
+
+ Returns True on success, False on Failure.
+ """
+ path = os.path.abspath(path)
+ if os.path.isdir(path):
+ logger.info('Installing from source directory: %r', path)
+ return _run_install_from_dir(path)
+ elif _is_archive_file(path):
+ logger.info('Installing from archive: %r', path)
+ _unpacked_dir = tempfile.mkdtemp()
+ try:
+ unpack_archive(path, _unpacked_dir)
+ return _run_install_from_archive(_unpacked_dir)
+ finally:
+ shutil.rmtree(_unpacked_dir)
+ else:
+ logger.warning('No project to install.')
+ return False
+
+
+def _run_install_from_archive(source_dir):
+ # XXX need a better way
+ for item in os.listdir(source_dir):
+ fullpath = os.path.join(source_dir, item)
+ if os.path.isdir(fullpath):
+ source_dir = fullpath
+ break
+ return _run_install_from_dir(source_dir)
+
+
+install_methods = {
+ 'distutils2': _run_packaging_install,
+ 'setuptools': _run_setuptools_install,
+ 'distutils': _run_distutils_install}
+
+
+def _run_install_from_dir(source_dir):
old_dir = os.getcwd()
- os.chdir(archive_dir)
+ os.chdir(source_dir)
+ install_method = get_install_method(source_dir)
+ func = install_methods[install_method]
try:
- # distutils2 or distutils1 ?
- if 'setup.py' in os.listdir(archive_dir):
- return _run_d1_install(archive_dir, path)
- else:
- return _run_d2_install(archive_dir, path)
+ func = install_methods[install_method]
+ try:
+ func(source_dir)
+ return True
+ except ValueError:
+ # failed to install
+ logger.info(str(sys.exc_info()[1]))
+ return False
finally:
os.chdir(old_dir)
-def install_dists(dists, path, paths=sys.path):
+def install_dists(dists, path, paths=None):
"""Install all distributions provided in dists, with the given prefix.
If an error occurs while installing one of the distributions, uninstall all
the installed distribution (in the context if this function).
- Return a list of installed files.
+ Return a list of installed dists.
:param dists: distributions to install
:param path: base path to install distribution in
:param paths: list of paths (defaults to sys.path) to look for info
"""
- installed_dists, installed_files = [], []
+ installed_dists = []
for dist in dists:
- logger.info('installing %s %s', dist.name, dist.version)
+ logger.info('Installing %r %s...', dist.name, dist.version)
try:
- installed_files.extend(_install_dist(dist, path))
+ _install_dist(dist, path)
installed_dists.append(dist)
- except Exception, e:
- logger.info('failed: %s', e)
+ except Exception:
+ logger.info('Failed: %s', sys.exc_info()[1])
# reverting
for installed_dist in installed_dists:
- _remove_dist(installed_dist, paths)
+ logger.info('Reverting %r', installed_dist)
+ remove(installed_dist.name, paths)
raise e
-
- return installed_files
+ return installed_dists
def install_from_infos(install_path=None, install=[], remove=[], conflicts=[],
- paths=sys.path):
+ paths=None):
"""Install and remove the given distributions.
The function signature is made to be compatible with the one of get_infos.
@@ -188,7 +243,7 @@
if remove:
temp_dir = tempfile.mkdtemp()
for dist in remove:
- files = dist.get_installed_files()
+ files = dist.list_installed_files()
temp_files[dist] = _move_files(files, temp_dir)
try:
if install:
@@ -236,23 +291,31 @@
Conflict contains all the conflicting distributions, if there is a
conflict.
"""
+ # this function does several things:
+ # 1. get a release specified by the requirements
+ # 2. gather its metadata, using setuptools compatibility if needed
+ # 3. compare this tree with what is currently installed on the system,
+ # return the requirements of what is missing
+ # 4. do that recursively and merge back the results
+ # 5. return a dict containing information about what is needed to install
+ # or remove
+
if not installed:
- logger.info('reading installed distributions')
- installed = get_distributions(use_egg_info=True)
+ logger.debug('Reading installed distributions')
+ installed = list(get_distributions(use_egg_info=True))
infos = {'install': [], 'remove': [], 'conflict': []}
- # Is a compatible version of the project is already installed ?
+ # Is a compatible version of the project already installed ?
predicate = get_version_predicate(requirements)
found = False
- installed = list(installed)
- # check that the project isnt already installed
+ # check that the project isn't already installed
for installed_project in installed:
# is it a compatible project ?
if predicate.name.lower() != installed_project.name.lower():
continue
found = True
- logger.info('found %s %s', installed_project.name,
+ logger.info('Found %r %s', installed_project.name,
installed_project.version)
# if we already have something installed, check it matches the
@@ -262,50 +325,45 @@
break
if not found:
- logger.info('project not installed')
+ logger.debug('Project not installed')
if not index:
index = wrapper.ClientWrapper()
+ if not installed:
+ installed = get_distributions(use_egg_info=True)
+
# Get all the releases that match the requirements
try:
- releases = index.get_releases(requirements)
+ release = index.get_release(requirements)
except (ReleaseNotFound, ProjectNotFound):
- raise InstallationException('Release not found: "%s"' % requirements)
-
- # Pick up a release, and try to get the dependency tree
- release = releases.get_last(requirements, prefer_final=prefer_final)
+ raise InstallationException('Release not found: %r' % requirements)
if release is None:
- logger.info('could not find a matching project')
+ logger.info('Could not find a matching project')
return infos
- # this works for Metadata 1.2
metadata = release.fetch_metadata()
- # for earlier, we need to build setuptools deps if any
+ # we need to build setuptools deps if any
if 'requires_dist' not in metadata:
- deps = _get_setuptools_deps(release)
- else:
- deps = metadata['requires_dist']
+ metadata['requires_dist'] = _get_setuptools_deps(release)
- # XXX deps not used
+ # build the dependency graph with local and required dependencies
+ dists = list(installed)
+ dists.append(release)
+ depgraph = generate_graph(dists)
- distributions = itertools.chain(installed, [release])
- depgraph = generate_graph(distributions)
-
- # Store all the already_installed packages in a list, in case of rollback.
# Get what the missing deps are
dists = depgraph.missing[release]
if dists:
- logger.info("missing dependencies found, retrieving metadata")
+ logger.info("Missing dependencies found, retrieving metadata")
# we have missing deps
for dist in dists:
_update_infos(infos, get_infos(dist, index, installed))
# Fill in the infos
existing = [d for d in installed if d.name == release.name]
-
if existing:
infos['remove'].append(existing[0])
infos['conflict'].extend(depgraph.reverse_list[existing[0]])
@@ -322,26 +380,38 @@
infos[key].extend(new_infos[key])
-def _remove_dist(dist, paths=sys.path):
- remove(dist.name, paths)
+def remove(project_name, paths=None, auto_confirm=True):
+ """Removes a single project from the installation.
-
-def remove(project_name, paths=sys.path):
- """Removes a single project from the installation"""
+ Returns True on success
+ """
dist = get_distribution(project_name, use_egg_info=True, paths=paths)
if dist is None:
- raise DistutilsError('Distribution "%s" not found' % project_name)
- files = dist.get_installed_files(local=True)
+ raise PackagingError('Distribution %r not found' % project_name)
+ files = dist.list_installed_files(local=True)
rmdirs = []
rmfiles = []
tmp = tempfile.mkdtemp(prefix=project_name + '-uninstall')
+
+ def _move_file(source, target):
+ try:
+ os.rename(source, target)
+ except OSError:
+ return sys.exc_info()[1]
+ return None
+
+ success = True
+ error = None
try:
for file_, md5, size in files:
if os.path.isfile(file_):
dirname, filename = os.path.split(file_)
tmpfile = os.path.join(tmp, filename)
try:
- os.rename(file_, tmpfile)
+ error = _move_file(file_, tmpfile)
+ if error is not None:
+ success = False
+ break
finally:
if not os.path.isfile(file_):
os.rename(tmpfile, file_)
@@ -352,72 +422,119 @@
finally:
shutil.rmtree(tmp)
- logger.info('removing %r...', project_name)
+ if not success:
+ logger.info('%r cannot be removed.', project_name)
+ logger.info('Error: %s', error)
+ return False
- file_count = 0
+ logger.info('Removing %r: ', project_name)
+
for file_ in rmfiles:
- os.remove(file_)
- file_count += 1
+ logger.info(' %s', file_)
- dir_count = 0
- for dirname in rmdirs:
- if not os.path.exists(dirname):
- # could
- continue
+ # Taken from the pip project
+ if auto_confirm:
+ response = 'y'
+ else:
+ response = ask('Proceed (y/n)? ', ('y', 'n'))
- files_count = 0
- for root, dir, files in os.walk(dirname):
- files_count += len(files)
+ if response == 'y':
+ file_count = 0
+ for file_ in rmfiles:
+ os.remove(file_)
+ file_count += 1
- if files_count > 0:
- # XXX Warning
- continue
+ dir_count = 0
+ for dirname in rmdirs:
+ if not os.path.exists(dirname):
+ # could
+ continue
- # empty dirs with only empty dirs
- if bool(os.stat(dirname).st_mode & stat.S_IWUSR):
- # XXX Add a callable in shutil.rmtree to count
- # the number of deleted elements
- shutil.rmtree(dirname)
- dir_count += 1
+ files_count = 0
+ for root, dir, files in os.walk(dirname):
+ files_count += len(files)
- # removing the top path
- # XXX count it ?
- if os.path.exists(dist.path):
- shutil.rmtree(dist.path)
+ if files_count > 0:
+ # XXX Warning
+ continue
- logger.info('success: removed %d files and %d dirs',
- file_count, dir_count)
+ # empty dirs with only empty dirs
+ if os.stat(dirname).st_mode & stat.S_IWUSR:
+ # XXX Add a callable in shutil.rmtree to count
+ # the number of deleted elements
+ shutil.rmtree(dirname)
+ dir_count += 1
+
+ # removing the top path
+ # XXX count it ?
+ if os.path.exists(dist.path):
+ shutil.rmtree(dist.path)
+
+ logger.info('Success: removed %d files and %d dirs',
+ file_count, dir_count)
+
+ return True
def install(project):
- logger.info('getting information about %r', project)
+ """Installs a project.
+
+ Returns True on success, False on failure
+ """
+ if is_python_build():
+ # Python would try to install into the site-packages directory under
+ # $PREFIX, but when running from an uninstalled code checkout we don't
+ # want to create directories under the installation root
+ message = ('installing third-party projects from an uninstalled '
+ 'Python is not supported')
+ logger.error(message)
+ return False
+
+ logger.info('Checking the installation location...')
+ purelib_path = get_path('purelib')
+
+ # trying to write a file there
+ try:
+ with tempfile.NamedTemporaryFile(suffix=project,
+ dir=purelib_path) as testfile:
+ testfile.write(b'test')
+ except OSError:
+ # FIXME this should check the errno, or be removed altogether (race
+ # condition: the directory permissions could be changed between here
+ # and the actual install)
+ logger.info('Unable to write in "%s". Do you have the permissions ?'
+ % purelib_path)
+ return False
+
+ logger.info('Getting information about %r...', project)
try:
info = get_infos(project)
except InstallationException:
- logger.info('cound not find %r', project)
- return
+ logger.info('Cound not find %r', project)
+ return False
if info['install'] == []:
- logger.info('nothing to install')
- return
+ logger.info('Nothing to install')
+ return False
install_path = get_config_var('base')
try:
install_from_infos(install_path,
info['install'], info['remove'], info['conflict'])
- except InstallationConflict, e:
+ except InstallationConflict:
+ e = sys.exc_info()[1]
if logger.isEnabledFor(logging.INFO):
- projects = ['%s %s' % (p.name, p.version) for p in e.args[0]]
+ projects = ('%r %s' % (p.name, p.version) for p in e.args[0])
logger.info('%r conflicts with %s', project, ','.join(projects))
+ return True
+
def _main(**attrs):
if 'script_args' not in attrs:
- import sys
attrs['requirements'] = sys.argv[1]
get_infos(**attrs)
-
if __name__ == '__main__':
_main()
diff --git a/distutils2/manifest.py b/distutils2/manifest.py
--- a/distutils2/manifest.py
+++ b/distutils2/manifest.py
@@ -1,22 +1,20 @@
-"""distutils2.manifest
+"""Class representing the list of files in a distribution.
-Provides a Manifest class that can be used to:
+The Manifest class can be used to:
- read or write a MANIFEST file
- read a template file and find out the file list
-
-Basically, Manifest *is* the file list.
-
-XXX todo: document + add tests
"""
+# XXX todo: document + add tests
import re
import os
+import sys
import fnmatch
-import logging
+from distutils2 import logger
from distutils2.util import write_file, convert_path
-from distutils2.errors import (DistutilsTemplateError,
- DistutilsInternalError)
+from distutils2.errors import (PackagingTemplateError,
+ PackagingInternalError)
__all__ = ['Manifest']
@@ -24,6 +22,7 @@
_COLLAPSE_PATTERN = re.compile('\\\w*\n', re.M)
_COMMENTED_LINE = re.compile('#.*?(?=\n)|\n(?=$)', re.M | re.S)
+
class Manifest(object):
"""A list of files built by on exploring the filesystem and filtered by
applying various patterns to what we find there.
@@ -48,11 +47,8 @@
def sort(self):
# Not a strict lexical sort!
- sortable_files = map(os.path.split, self.files)
- sortable_files.sort()
- self.files = []
- for sort_tuple in sortable_files:
- self.files.append(os.path.join(*sort_tuple))
+ self.files = [os.path.join(*path_tuple) for path_tuple in
+ sorted(os.path.split(path) for path in self.files)]
def clear(self):
"""Clear all collected files."""
@@ -72,7 +68,7 @@
Updates the list accordingly.
"""
- if isinstance(path_or_file, str):
+ if isinstance(path_or_file, basestring):
f = open(path_or_file)
else:
f = path_or_file
@@ -94,8 +90,8 @@
continue
try:
self._process_template_line(line)
- except DistutilsTemplateError, msg:
- logging.warning("%s, %s", path_or_file, msg)
+ except PackagingTemplateError:
+ logger.warning("%s, %s", path_or_file, sys.exc_info()[1])
def write(self, path):
"""Write the file list in 'self.filelist' (presumably as filled in
@@ -103,22 +99,19 @@
named by 'self.manifest'.
"""
if os.path.isfile(path):
- fp = open(path)
- try:
+ with open(path) as fp:
first_line = fp.readline()
- finally:
- fp.close()
- if first_line != '# file GENERATED by distutils, do NOT edit\n':
- logging.info("not writing to manually maintained "
- "manifest file %r", path)
+ if first_line != '# file GENERATED by distutils2, do NOT edit\n':
+ logger.info("not writing to manually maintained "
+ "manifest file %r", path)
return
self.sort()
self.remove_duplicates()
content = self.files[:]
- content.insert(0, '# file GENERATED by distutils, do NOT edit')
- logging.info("writing manifest file %r", path)
+ content.insert(0, '# file GENERATED by distutils2, do NOT edit')
+ logger.info("writing manifest file %r", path)
write_file(path, content)
def read(self, path):
@@ -126,28 +119,26 @@
fill in 'self.filelist', the list of files to include in the source
distribution.
"""
- logging.info("reading manifest file %r", path)
- manifest = open(path)
- try:
+ logger.info("reading manifest file %r", path)
+ with open(path) as manifest:
for line in manifest.readlines():
self.append(line)
- finally:
- manifest.close()
- def exclude_pattern(self, pattern, anchor=1, prefix=None, is_regex=0):
+ 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 1 if files are
+ The list 'self.files' is modified in place. Return True if files are
found.
"""
- files_found = 0
+ files_found = False
pattern_re = _translate_pattern(pattern, anchor, prefix, is_regex)
- for i in range(len(self.files)-1, -1, -1):
+ for i in range(len(self.files) - 1, -1, -1):
if pattern_re.search(self.files[i]):
del self.files[i]
- files_found = 1
+ files_found = True
return files_found
@@ -167,28 +158,28 @@
if action in ('include', 'exclude',
'global-include', 'global-exclude'):
if len(words) < 2:
- raise DistutilsTemplateError(
+ raise PackagingTemplateError(
"%r expects <pattern1> <pattern2> ..." % action)
- patterns = map(convert_path, words[1:])
+ patterns = [convert_path(word) for word in words[1:]]
elif action in ('recursive-include', 'recursive-exclude'):
if len(words) < 3:
- raise DistutilsTemplateError(
+ raise PackagingTemplateError(
"%r expects <dir> <pattern1> <pattern2> ..." % action)
dir = convert_path(words[1])
- patterns = map(convert_path, words[2:])
+ patterns = [convert_path(word) for word in words[2:]]
elif action in ('graft', 'prune'):
if len(words) != 2:
- raise DistutilsTemplateError(
+ raise PackagingTemplateError(
"%r expects a single <dir_pattern>" % action)
dir_pattern = convert_path(words[1])
else:
- raise DistutilsTemplateError("unknown action %r" % action)
+ raise PackagingTemplateError("unknown action %r" % action)
return action, patterns, dir, dir_pattern
@@ -205,55 +196,56 @@
# can proceed with minimal error-checking.
if action == 'include':
for pattern in patterns:
- if not self._include_pattern(pattern, anchor=1):
- logging.warning("no files found matching %r", pattern)
+ if not self._include_pattern(pattern, anchor=True):
+ logger.warning("no files found matching %r", pattern)
elif action == 'exclude':
for pattern in patterns:
- if not self.exclude_pattern(pattern, anchor=1):
- logging.warning("no previously-included files "
- "found matching %r", pattern)
+ if not self.exclude_pattern(pattern, anchor=True):
+ logger.warning("no previously-included files "
+ "found matching %r", pattern)
elif action == 'global-include':
for pattern in patterns:
- if not self._include_pattern(pattern, anchor=0):
- logging.warning("no files found matching %r "
- "anywhere in distribution", pattern)
+ 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:
- if not self.exclude_pattern(pattern, anchor=0):
- logging.warning("no previously-included files "
- "matching %r found anywhere in "
- "distribution", pattern)
+ if not self.exclude_pattern(pattern, anchor=False):
+ logger.warning("no previously-included files "
+ "matching %r found anywhere in "
+ "distribution", pattern)
elif action == 'recursive-include':
for pattern in patterns:
if not self._include_pattern(pattern, prefix=dir):
- logging.warning("no files found matching %r "
- "under directory %r", pattern, dir)
+ logger.warning("no files found matching %r "
+ "under directory %r", pattern, dir)
elif action == 'recursive-exclude':
for pattern in patterns:
if not self.exclude_pattern(pattern, prefix=dir):
- logging.warning("no previously-included files "
- "matching %r found under directory %r",
- pattern, dir)
+ logger.warning("no previously-included files "
+ "matching %r found under directory %r",
+ pattern, dir)
elif action == 'graft':
if not self._include_pattern(None, prefix=dir_pattern):
- logging.warning("no directories found matching %r",
- dir_pattern)
+ logger.warning("no directories found matching %r",
+ dir_pattern)
elif action == 'prune':
if not self.exclude_pattern(None, prefix=dir_pattern):
- logging.warning("no previously-included directories found "
- "matching %r", dir_pattern)
+ logger.warning("no previously-included directories found "
+ "matching %r", dir_pattern)
else:
- raise DistutilsInternalError(
- "this cannot happen: invalid action %r" % action)
+ raise PackagingInternalError(
+ "this cannot happen: invalid action %r" % action)
- def _include_pattern(self, pattern, anchor=1, prefix=None, is_regex=0):
+ 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.
@@ -277,9 +269,9 @@
Selected strings will be added to self.files.
- Return 1 if files are found.
+ Return True if files are found.
"""
- files_found = 0
+ files_found = False
pattern_re = _translate_pattern(pattern, anchor, prefix, is_regex)
# delayed loading of allfiles list
@@ -289,21 +281,19 @@
for name in self.allfiles:
if pattern_re.search(name):
self.files.append(name)
- files_found = 1
+ files_found = True
return files_found
-
#
# Utility functions
#
-
def _findall(dir=os.curdir):
"""Find all files under 'dir' and return the list of full filenames
(relative to 'dir').
"""
- from stat import ST_MODE, S_ISREG, S_ISDIR, S_ISLNK
+ from stat import S_ISREG, S_ISDIR, S_ISLNK
list = []
stack = [dir]
@@ -322,7 +312,7 @@
# Avoid excess stat calls -- just one will do, thank you!
stat = os.stat(fullname)
- mode = stat[ST_MODE]
+ mode = stat.st_mode
if S_ISREG(mode):
list.append(fullname)
elif S_ISDIR(mode) and not S_ISLNK(mode):
@@ -331,7 +321,6 @@
return list
-
def _glob_to_re(pattern):
"""Translate a shell-like glob pattern to a regular expression.
@@ -353,7 +342,7 @@
return pattern_re
-def _translate_pattern(pattern, anchor=1, prefix=None, is_regex=0):
+def _translate_pattern(pattern, anchor=True, prefix=None, is_regex=False):
"""Translate a shell-like wildcard pattern to a compiled regular
expression.
@@ -362,7 +351,7 @@
or just returned as-is (assumes it's a regex object).
"""
if is_regex:
- if isinstance(pattern, str):
+ if isinstance(pattern, basestring):
return re.compile(pattern)
else:
return pattern
diff --git a/distutils2/markers.py b/distutils2/markers.py
--- a/distutils2/markers.py
+++ b/distutils2/markers.py
@@ -1,10 +1,11 @@
-""" Micro-language for PEP 345 environment markers
-"""
+"""Parser for the environment markers micro-language defined in PEP 345."""
+
import sys
import platform
import os
-from tokenize import tokenize, NAME, OP, STRING, ENDMARKER
-from StringIO import StringIO
+
+from tokenize import generate_tokens, NAME, OP, STRING, ENDMARKER
+from StringIO import StringIO as BytesIO
__all__ = ['interpret']
@@ -30,7 +31,8 @@
'python_full_version': sys.version.split(' ', 1)[0],
'os.name': os.name,
'platform.version': platform.version(),
- 'platform.machine': platform.machine()}
+ 'platform.machine': platform.machine(),
+ 'platform.python_implementation': platform.python_implementation()}
class _Operation(object):
@@ -124,39 +126,39 @@
return self.left() and self.right()
-class _CHAIN(object):
-
- def __init__(self, execution_context=None):
- self.ops = []
- self.op_starting = True
- self.execution_context = execution_context
-
- def eat(self, toktype, tokval, rowcol, line, logical_line):
+def interpret(marker, execution_context=None):
+ """Interpret a marker and return a result depending on environment."""
+ marker = marker.strip().encode()
+ ops = []
+ op_starting = True
+ for token in generate_tokens(BytesIO(marker).readline):
+ # Unpack token
+ toktype, tokval, rowcol, line, logical_line = token
if toktype not in (NAME, OP, STRING, ENDMARKER):
raise SyntaxError('Type not supported "%s"' % tokval)
- if self.op_starting:
- op = _Operation(self.execution_context)
- if len(self.ops) > 0:
- last = self.ops[-1]
+ if op_starting:
+ op = _Operation(execution_context)
+ if len(ops) > 0:
+ last = ops[-1]
if isinstance(last, (_OR, _AND)) and not last.filled():
last.right = op
else:
- self.ops.append(op)
+ ops.append(op)
else:
- self.ops.append(op)
- self.op_starting = False
+ ops.append(op)
+ op_starting = False
else:
- op = self.ops[-1]
+ op = ops[-1]
if (toktype == ENDMARKER or
(toktype == NAME and tokval in ('and', 'or'))):
if toktype == NAME and tokval == 'and':
- self.ops.append(_AND(self.ops.pop()))
+ ops.append(_AND(ops.pop()))
elif toktype == NAME and tokval == 'or':
- self.ops.append(_OR(self.ops.pop()))
- self.op_starting = True
- return
+ ops.append(_OR(ops.pop()))
+ op_starting = True
+ continue
if isinstance(op, (_OR, _AND)) and op.right is not None:
op = op.right
@@ -179,16 +181,7 @@
else:
op.op = tokval
- def result(self):
- for op in self.ops:
- if not op():
- return False
- return True
-
-
-def interpret(marker, execution_context=None):
- """Interpret a marker and return a result depending on environment."""
- marker = marker.strip()
- operations = _CHAIN(execution_context)
- tokenize(StringIO(marker).readline, operations.eat)
- return operations.result()
+ for op in ops:
+ if not op():
+ return False
+ return True
diff --git a/distutils2/metadata.py b/distutils2/metadata.py
--- a/distutils2/metadata.py
+++ b/distutils2/metadata.py
@@ -3,17 +3,19 @@
Supports all metadata formats (1.0, 1.1, 1.2).
"""
+import codecs
import re
+import logging
+
from StringIO import StringIO
from email import message_from_file
-
from distutils2 import logger
from distutils2.markers import interpret
from distutils2.version import (is_valid_predicate, is_valid_version,
- is_valid_versions)
+ is_valid_versions)
from distutils2.errors import (MetadataMissingError,
- MetadataConflictError,
- MetadataUnrecognizedVersionError)
+ MetadataConflictError,
+ MetadataUnrecognizedVersionError)
try:
# docutils is installed
@@ -39,8 +41,7 @@
_HAS_DOCUTILS = False
# public API of this module
-__all__ = ['Metadata', 'get_metadata_version', 'metadata_to_dict',
- 'PKG_INFO_ENCODING', 'PKG_INFO_PREFERRED_VERSION']
+__all__ = ['Metadata', 'PKG_INFO_ENCODING', 'PKG_INFO_PREFERRED_VERSION']
# Encoding used for the PKG-INFO files
PKG_INFO_ENCODING = 'utf-8'
@@ -99,7 +100,7 @@
return True
return False
- keys = fields.keys()
+ keys = list(fields)
possible_versions = ['1.0', '1.1', '1.2']
# first let's try to see if a field is not part of one of the version
@@ -138,52 +139,6 @@
return '1.2'
-def get_metadata_version(metadata):
- """Return the Metadata-Version attribute
-
- - *metadata* give a METADATA object
- """
- return metadata['Metadata-Version']
-
-
-def metadata_to_dict(metadata):
- """Convert a metadata object to a dict
-
- - *metadata* give a METADATA object
- """
- data = {
- 'metadata_version': metadata['Metadata-Version'],
- 'name': metadata['Name'],
- 'version': metadata['Version'],
- 'summary': metadata['Summary'],
- 'home_page': metadata['Home-page'],
- 'author': metadata['Author'],
- 'author_email': metadata['Author-email'],
- 'license': metadata['License'],
- 'description': metadata['Description'],
- 'keywords': metadata['Keywords'],
- 'platform': metadata['Platform'],
- 'classifier': metadata['Classifier'],
- 'download_url': metadata['Download-URL'],
- }
-
- if metadata['Metadata-Version'] == '1.2':
- data['requires_dist'] = metadata['Requires-Dist']
- data['requires_python'] = metadata['Requires-Python']
- data['requires_external'] = metadata['Requires-External']
- data['provides_dist'] = metadata['Provides-Dist']
- data['obsoletes_dist'] = metadata['Obsoletes-Dist']
- data['project_url'] = [','.join(url) for url in
- metadata['Project-URL']]
-
- elif metadata['Metadata-Version'] == '1.1':
- data['provides'] = metadata['Provides']
- data['requires'] = metadata['Requires']
- data['obsoletes'] = metadata['Obsoletes']
-
- return data
-
-
_ATTR2FIELD = {
'metadata_version': 'Metadata-Version',
'name': 'Name',
@@ -225,6 +180,8 @@
_UNICODEFIELDS = ('Author', 'Maintainer', 'Summary', 'Description')
+_MISSING = object()
+
class NoDefault(object):
"""Marker object used for clean representation"""
@@ -248,10 +205,8 @@
# also document the mapping API and UNKNOWN default key
def __init__(self, path=None, platform_dependent=False,
- execution_context=None, fileobj=None, mapping=None,
- display_warnings=False):
+ execution_context=None, fileobj=None, mapping=None):
self._fields = {}
- self.display_warnings = display_warnings
self.requires_files = []
self.docutils_support = _HAS_DOCUTILS
self.platform_dependent = platform_dependent
@@ -269,12 +224,7 @@
self._fields['Metadata-Version'] = _best_version(self._fields)
def _write_field(self, file, name, value):
- file.write('%s: %s\n' % (name, value))
-
- def _encode_field(self, value):
- if isinstance(value, unicode):
- return value.encode(PKG_INFO_ENCODING)
- return str(value)
+ file.write(u'%s: %s\n' % (name, value))
def __getitem__(self, name):
return self.get(name)
@@ -358,7 +308,8 @@
def read(self, filepath):
"""Read the metadata values from a file path."""
- self.read_file(open(filepath))
+ with codecs.open(filepath, 'r', encoding='utf-8') as fp:
+ self.read_file(fp)
def read_file(self, fileob):
"""Read the metadata values from a file object."""
@@ -380,11 +331,8 @@
def write(self, filepath):
"""Write the metadata fields to filepath."""
- pkg_info = open(filepath, 'w')
- try:
- self.write_file(pkg_info)
- finally:
- pkg_info.close()
+ with codecs.open(filepath, 'w', encoding='utf-8') as fp:
+ self.write_file(fp)
def write_file(self, fileobject):
"""Write the PKG-INFO format data to a file object."""
@@ -437,36 +385,38 @@
if ((name in _ELEMENTSFIELD or name == 'Platform') and
not isinstance(value, (list, tuple))):
- if isinstance(value, str):
+ if isinstance(value, basestring):
value = [v.strip() for v in value.split(',')]
else:
value = []
elif (name in _LISTFIELDS and
not isinstance(value, (list, tuple))):
- if isinstance(value, str):
+ if isinstance(value, basestring):
value = [value]
else:
value = []
- if self.display_warnings:
+ if logger.isEnabledFor(logging.WARNING):
+ project_name = self['Name']
+
if name in _PREDICATE_FIELDS and value is not None:
for v in value:
# check that the values are valid predicates
if not is_valid_predicate(v.split(';')[0]):
- logger.warn('"%s" is not a valid predicate (field "%s")' %
- (v, name))
+ logger.warning(
+ '%r: %r is not a valid predicate (field %r)',
+ project_name, v, name)
# FIXME this rejects UNKNOWN, is that right?
elif name in _VERSIONS_FIELDS and value is not None:
if not is_valid_versions(value):
- logger.warn('"%s" is not a valid version (field "%s")' %
- (value, name))
+ logger.warning('%r: %r is not a valid version (field %r)',
+ project_name, value, name)
elif name in _VERSION_FIELDS and value is not None:
if not is_valid_version(value):
- logger.warn('"%s" is not a valid version (field "%s")' %
- (value, name))
+ logger.warning('%r: %r is not a valid version (field %r)',
+ project_name, value, name)
if name in _UNICODEFIELDS:
- value = self._encode_field(value)
if name == 'Description':
value = self._remove_line_prefix(value)
@@ -482,7 +432,7 @@
return default
if name in _UNICODEFIELDS:
value = self._fields[name]
- return self._encode_field(value)
+ return value
elif name in _LISTFIELDS:
value = self._fields[name]
if value is None:
@@ -493,17 +443,17 @@
if not valid:
continue
if name not in _LISTTUPLEFIELDS:
- res.append(self._encode_field(val))
+ res.append(val)
else:
# That's for Project-URL
- res.append((self._encode_field(val[0]), val[1]))
+ res.append((val[0], val[1]))
return res
elif name in _ELEMENTSFIELD:
valid, value = self._platform(self._fields[name])
if not valid:
return []
- if isinstance(value, str):
+ if isinstance(value, basestring):
return value.split(',')
valid, value = self._platform(self._fields[name])
if not valid:
@@ -551,13 +501,55 @@
return missing, warnings
+ def todict(self):
+ """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).
+ """
+ data = {
+ 'metadata_version': self['Metadata-Version'],
+ 'name': self['Name'],
+ 'version': self['Version'],
+ 'summary': self['Summary'],
+ 'home_page': self['Home-page'],
+ 'author': self['Author'],
+ 'author_email': self['Author-email'],
+ 'license': self['License'],
+ 'description': self['Description'],
+ 'keywords': self['Keywords'],
+ 'platform': self['Platform'],
+ 'classifier': self['Classifier'],
+ 'download_url': self['Download-URL'],
+ }
+
+ if self['Metadata-Version'] == '1.2':
+ data['requires_dist'] = self['Requires-Dist']
+ data['requires_python'] = self['Requires-Python']
+ data['requires_external'] = self['Requires-External']
+ data['provides_dist'] = self['Provides-Dist']
+ data['obsoletes_dist'] = self['Obsoletes-Dist']
+ data['project_url'] = [','.join(url) for url in
+ self['Project-URL']]
+
+ elif self['Metadata-Version'] == '1.1':
+ data['provides'] = self['Provides']
+ data['requires'] = self['Requires']
+ data['obsoletes'] = self['Obsoletes']
+
+ return data
+
# Mapping API
def keys(self):
return _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()]
+ return [self[key] for key in list(self.keys())]
def items(self):
- return [(key, self[key]) for key in self.keys()]
+ return [(key, self[key]) for key in list(self.keys())]
diff --git a/distutils2/pypi/__init__.py b/distutils2/pypi/__init__.py
--- a/distutils2/pypi/__init__.py
+++ b/distutils2/pypi/__init__.py
@@ -1,6 +1,4 @@
-"""Package containing ways to interact with Index APIs.
-
-"""
+"""Low-level and high-level APIs to interact with project indexes."""
__all__ = ['simple',
'xmlrpc',
@@ -8,4 +6,4 @@
'errors',
'mirrors']
-from dist import ReleaseInfo, ReleasesList, DistInfo
+from distutils2.pypi.dist import ReleaseInfo, ReleasesList, DistInfo
diff --git a/distutils2/pypi/base.py b/distutils2/pypi/base.py
--- a/distutils2/pypi/base.py
+++ b/distutils2/pypi/base.py
@@ -1,4 +1,6 @@
-from distutils2.index.dist import ReleasesList
+"""Base class for index crawlers."""
+
+from distutils2.pypi.dist import ReleasesList
class BaseClient(object):
diff --git a/distutils2/pypi/dist.py b/distutils2/pypi/dist.py
--- a/distutils2/pypi/dist.py
+++ b/distutils2/pypi/dist.py
@@ -1,35 +1,25 @@
-"""distutils2.index.dist
+"""Classes representing releases and distributions retrieved from indexes.
-Provides useful classes to represent the release and distributions retrieved
-from indexes.
+A project (= unique name) can have several releases (= versions) and
+each release can have several distributions (= sdist and bdists).
-A project can have several releases (=versions) and each release can have
-several distributions (sdist, bdist).
+Release objects contain metadata-related information (see PEP 376);
+distribution objects contain download-related information.
+"""
-The release contains the metadata related informations (see PEP 384), and the
-distributions contains download related informations.
-
-"""
-import mimetypes
import re
-import tarfile
+import hashlib
import tempfile
import urllib
import urlparse
-import zipfile
-try:
- import hashlib
-except ImportError:
- from distutils2._backport import hashlib
+from distutils2.errors import IrrationalVersionError
+from distutils2.version import (suggest_normalized_version, NormalizedVersion,
+ get_version_predicate)
+from distutils2.metadata import Metadata
+from distutils2.pypi.errors import (HashDoesNotMatch, UnsupportedHashName,
+ CantParseArchiveName)
+from distutils2.util import unpack_archive
-from distutils2._backport.shutil import unpack_archive
-from distutils2.errors import IrrationalVersionError
-from distutils2.index.errors import (HashDoesNotMatch, UnsupportedHashName,
- CantParseArchiveName)
-from distutils2.version import (suggest_normalized_version, NormalizedVersion,
- get_version_predicate)
-from distutils2.metadata import Metadata
-from distutils2.util import splitext
__all__ = ['ReleaseInfo', 'DistInfo', 'ReleasesList', 'get_infos_from_url']
@@ -94,7 +84,7 @@
def fetch_metadata(self):
"""If the metadata is not set, use the indexes to get it"""
if not self.metadata:
- self._index.get_metadata(self.name, '%s' % self.version)
+ self._index.get_metadata(self.name, str(self.version))
return self.metadata
@property
@@ -104,7 +94,7 @@
def fetch_distributions(self):
if self.dists is None:
- self._index.get_distributions(self.name, '%s' % self.version)
+ self._index.get_distributions(self.name, str(self.version))
if self.dists is None:
self.dists = {}
return self.dists
@@ -140,14 +130,14 @@
not return one existing distribution.
"""
if len(self.dists) == 0:
- raise LookupError()
+ raise LookupError
if dist_type:
return self[dist_type]
if prefer_source:
if "sdist" in self.dists:
dist = self["sdist"]
else:
- dist = self.dists.values()[0]
+ dist = next(self.dists.values())
return dist
def unpack(self, path=None, prefer_source=True):
@@ -254,14 +244,14 @@
self._url = None
self.add_url(url, hashname, hashval, is_external)
- def add_url(self, url, hashname=None, hashval=None, is_external=True):
+ def add_url(self, url=None, hashname=None, hashval=None, is_external=True):
"""Add a new url to the list of urls"""
if hashname is not None:
try:
hashlib.new(hashname)
except ValueError:
raise UnsupportedHashName(hashname)
- if not url in [u['url'] for u in self.urls]:
+ if url not in [u['url'] for u in self.urls]:
self.urls.append({
'url': url,
'hashname': hashname,
@@ -323,20 +313,21 @@
path = tempfile.mkdtemp()
filename = self.download(path)
- content_type = mimetypes.guess_type(filename)[0]
- self._unpacked_dir = unpack_archive(filename, path)
+ unpack_archive(filename, path)
+ self._unpacked_dir = path
- return self._unpacked_dir
+ return path
def _check_md5(self, filename):
"""Check that the md5 checksum of the given file matches the one in
url param"""
hashname = self.url['hashname']
expected_hashval = self.url['hashval']
- if not None in (expected_hashval, hashname):
- f = open(filename)
- hashval = hashlib.new(hashname)
- hashval.update(f.read())
+ if None not in (expected_hashval, hashname):
+ with open(filename, 'rb') as f:
+ hashval = hashlib.new(hashname)
+ hashval.update(f.read())
+
if hashval.hexdigest() != expected_hashval:
raise HashDoesNotMatch("got %s instead of %s"
% (hashval.hexdigest(), expected_hashval))
@@ -408,19 +399,19 @@
"""
if release:
if release.name.lower() != self.name.lower():
- raise ValueError("%s is not the same project than %s" %
+ raise ValueError("%s is not the same project as %s" %
(release.name, self.name))
- version = '%s' % release.version
+ version = str(release.version)
- if not version in self.get_versions():
+ if version not in self.get_versions():
# append only if not already exists
self.releases.append(release)
- for dist in release.dists.itervalues():
+ for dist in release.dists.values():
for url in dist.urls:
self.add_release(version, dist.dist_type, **url)
else:
- matches = [r for r in self.releases if '%s' % r.version == version
- and r.name == self.name]
+ matches = [r for r in self.releases
+ if str(r.version) == version and r.name == self.name]
if not matches:
release = ReleaseInfo(self.name, version, index=self._index)
self.releases.append(release)
@@ -448,19 +439,19 @@
sort_by.append("version")
self.releases.sort(
- key=lambda i: [getattr(i, arg) for arg in sort_by],
+ key=lambda i: tuple(getattr(i, arg) for arg in sort_by),
reverse=reverse, *args, **kwargs)
def get_release(self, version):
"""Return a release from its version."""
- matches = [r for r in self.releases if "%s" % r.version == version]
+ matches = [r for r in self.releases if str(r.version) == version]
if len(matches) != 1:
raise KeyError(version)
return matches[0]
def get_versions(self):
"""Return a list of releases versions contained"""
- return ["%s" % r.version for r in self.releases]
+ return [str(r.version) for r in self.releases]
def __getitem__(self, key):
return self.releases[key]
@@ -532,7 +523,7 @@
# we dont get a good version number: recurse !
return eager_split(str, maxsplit - 1)
else:
- return (name, version)
+ return name, version
if probable_name is not None:
probable_name = probable_name.lower()
name = None
@@ -545,6 +536,6 @@
version = suggest_normalized_version(version)
if version is not None and name != "":
- return (name.lower(), version)
+ return name.lower(), version
else:
raise CantParseArchiveName(archive_name)
diff --git a/distutils2/pypi/errors.py b/distutils2/pypi/errors.py
--- a/distutils2/pypi/errors.py
+++ b/distutils2/pypi/errors.py
@@ -1,27 +1,25 @@
-"""distutils2.pypi.errors
+"""Exceptions raised by distutils2.pypi code."""
-All errors and exceptions raised by PyPiIndex classes.
-"""
-from distutils2.errors import DistutilsIndexError
+from distutils2.errors import PackagingPyPIError
-class ProjectNotFound(DistutilsIndexError):
+class ProjectNotFound(PackagingPyPIError):
"""Project has not been found"""
-class DistributionNotFound(DistutilsIndexError):
+class DistributionNotFound(PackagingPyPIError):
"""The release has not been found"""
-class ReleaseNotFound(DistutilsIndexError):
+class ReleaseNotFound(PackagingPyPIError):
"""The release has not been found"""
-class CantParseArchiveName(DistutilsIndexError):
+class CantParseArchiveName(PackagingPyPIError):
"""An archive name can't be parsed to find distribution name and version"""
-class DownloadError(DistutilsIndexError):
+class DownloadError(PackagingPyPIError):
"""An error has occurs while downloading"""
@@ -29,13 +27,13 @@
"""Compared hashes does not match"""
-class UnsupportedHashName(DistutilsIndexError):
+class UnsupportedHashName(PackagingPyPIError):
"""A unsupported hashname has been used"""
-class UnableToDownload(DistutilsIndexError):
+class UnableToDownload(PackagingPyPIError):
"""All mirrors have been tried, without success"""
-class InvalidSearchField(DistutilsIndexError):
+class InvalidSearchField(PackagingPyPIError):
"""An invalid search field has been used"""
diff --git a/distutils2/pypi/mirrors.py b/distutils2/pypi/mirrors.py
--- a/distutils2/pypi/mirrors.py
+++ b/distutils2/pypi/mirrors.py
@@ -1,6 +1,4 @@
-"""Utilities related to the mirror infrastructure defined in PEP 381.
-See http://www.python.org/dev/peps/pep-0381/
-"""
+"""Utilities related to the mirror infrastructure defined in PEP 381."""
from string import ascii_lowercase
import socket
@@ -12,7 +10,7 @@
"""Return the list of mirrors from the last record found on the DNS
entry::
- >>> from distutils2.index.mirrors import get_mirrors
+ >>> from distutils2.pypi.mirrors import get_mirrors
>>> get_mirrors()
['a.pypi.python.org', 'b.pypi.python.org', 'c.pypi.python.org',
'd.pypi.python.org']
@@ -46,7 +44,7 @@
def product(*args, **kwds):
- pools = map(tuple, args) * kwds.get('repeat', 1)
+ pools = [tuple(arg) for arg in args] * kwds.get('repeat', 1)
result = [[]]
for pool in pools:
result = [x + [y] for x in result for y in pool]
diff --git a/distutils2/pypi/simple.py b/distutils2/pypi/simple.py
--- a/distutils2/pypi/simple.py
+++ b/distutils2/pypi/simple.py
@@ -1,10 +1,11 @@
-"""index.simple
+"""Spider using the screen-scraping "simple" PyPI API.
-Contains the class "SimpleIndexCrawler", a simple spider to find and retrieve
-distributions on the Python Package Index, using its "simple" API,
-avalaible at http://pypi.python.org/simple/
+This module contains the class Crawler, a simple spider that
+can be used to find and retrieve distributions from a project index
+(like the Python Package Index), using its so-called simple API (see
+reference implementation available at http://pypi.python.org/simple/).
"""
-from fnmatch import translate
+
import httplib
import re
import socket
@@ -13,17 +14,20 @@
import urlparse
import os
+from fnmatch import translate
+from functools import wraps
from distutils2 import logger
-from distutils2.index.base import BaseClient
-from distutils2.index.dist import (ReleasesList, EXTENSIONS,
- get_infos_from_url, MD5_HASH)
-from distutils2.index.errors import (DistutilsIndexError, DownloadError,
- UnableToDownload, CantParseArchiveName,
- ReleaseNotFound, ProjectNotFound)
-from distutils2.index.mirrors import get_mirrors
from distutils2.metadata import Metadata
from distutils2.version import get_version_predicate
-from distutils2 import __version__ as __distutils2_version__
+from distutils2 import __version__ as distutils2_version
+from distutils2.pypi.base import BaseClient
+from distutils2.pypi.dist import (ReleasesList, EXTENSIONS,
+ get_infos_from_url, MD5_HASH)
+from distutils2.pypi.errors import (PackagingPyPIError, DownloadError,
+ UnableToDownload, CantParseArchiveName,
+ ReleaseNotFound, ProjectNotFound)
+from distutils2.pypi.mirrors import get_mirrors
+from distutils2.metadata import Metadata
__all__ = ['Crawler', 'DEFAULT_SIMPLE_INDEX_URL']
@@ -32,7 +36,7 @@
DEFAULT_HOSTS = ("*",)
SOCKET_TIMEOUT = 15
USER_AGENT = "Python-urllib/%s distutils2/%s" % (
- sys.version[:3], __distutils2_version__)
+ sys.version[:3], distutils2_version)
# -- Regexps -------------------------------------------------
EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.]+)$')
@@ -48,8 +52,9 @@
def socket_timeout(timeout=SOCKET_TIMEOUT):
"""Decorator to add a socket timeout when requesting pages on PyPI.
"""
- def _socket_timeout(func):
- def _socket_timeout(self, *args, **kwargs):
+ def wrapper(func):
+ @wraps(func)
+ def wrapped(self, *args, **kwargs):
old_timeout = socket.getdefaulttimeout()
if hasattr(self, "_timeout"):
timeout = self._timeout
@@ -58,13 +63,14 @@
return func(self, *args, **kwargs)
finally:
socket.setdefaulttimeout(old_timeout)
- return _socket_timeout
- return _socket_timeout
+ return wrapped
+ return wrapper
def with_mirror_support():
"""Decorator that makes the mirroring support easier"""
def wrapper(func):
+ @wraps(func)
def wrapped(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
@@ -103,7 +109,7 @@
:param follow_externals: tell if following external links is needed or
not. Default is False.
:param mirrors_url: the url to look on for DNS records giving mirror
- adresses.
+ addresses.
:param mirrors: a list of mirrors (see PEP 381).
:param timeout: time in seconds to consider a url has timeouted.
:param mirrors_max_tries": number of times to try requesting informations
@@ -113,13 +119,20 @@
def __init__(self, index_url=DEFAULT_SIMPLE_INDEX_URL, prefer_final=False,
prefer_source=True, hosts=DEFAULT_HOSTS,
follow_externals=False, mirrors_url=None, mirrors=None,
- timeout=SOCKET_TIMEOUT, mirrors_max_tries=0):
+ timeout=SOCKET_TIMEOUT, mirrors_max_tries=0, verbose=False):
super(Crawler, self).__init__(prefer_final, prefer_source)
self.follow_externals = follow_externals
+ self.verbose = verbose
# mirroring attributes.
- if not index_url.endswith("/"):
- index_url += "/"
+ parsed = urlparse.urlparse(index_url)
+ self.scheme = parsed[0]
+ if self.scheme == 'file':
+ ender = os.path.sep
+ else:
+ ender = '/'
+ if not index_url.endswith(ender):
+ index_url += ender
# if no mirrors are defined, use the method described in PEP 381.
if mirrors is None:
mirrors = get_mirrors(mirrors_url)
@@ -145,33 +158,39 @@
Return a list of names.
"""
- index = self._open_url(self.index_url)
- if '*' in name:
- name.replace('*', '.*')
- else:
- name = "%s%s%s" % ('*.?', name, '*.?')
- name = name.replace('*', '[^<]*') # avoid matching of the tag's end
- projectname = re.compile("""<a[^>]*>(%s)</a>""" % name, flags=re.I)
- matching_projects = []
- for match in projectname.finditer(index.read()):
+ with self._open_url(self.index_url) as index:
+ if '*' in name:
+ name.replace('*', '.*')
+ else:
+ name = "%s%s%s" % ('*.?', name, '*.?')
+ name = name.replace('*', '[^<]*') # avoid matching end tag
+ projectname = re.compile('<a[^>]*>(%s)</a>' % name, re.I)
+ matching_projects = []
+
+ index_content = index.read()
+
+ # FIXME should use bytes I/O and regexes instead of decoding
+ index_content = index_content.decode()
+
+ for match in projectname.finditer(index_content):
project_name = match.group(1)
matching_projects.append(self._get_project(project_name))
return matching_projects
def get_releases(self, requirements, prefer_final=None,
force_update=False):
- """Search for releases and return a ReleaseList object containing
+ """Search for releases and return a ReleasesList object containing
the results.
"""
predicate = get_version_predicate(requirements)
if predicate.name.lower() in self._projects and not force_update:
return self._projects.get(predicate.name.lower())
prefer_final = self._get_prefer_final(prefer_final)
- logger.info('reading info on PyPI about %s', predicate.name)
+ logger.debug('Reading info on PyPI about %s', predicate.name)
self._process_index_page(predicate.name)
if predicate.name.lower() not in self._projects:
- raise ProjectNotFound()
+ raise ProjectNotFound
releases = self._projects.get(predicate.name.lower())
releases.sort_releases(prefer_final=prefer_final)
@@ -199,10 +218,10 @@
Currently, download one archive, extract it and use the PKG-INFO file.
"""
release = self.get_distributions(project_name, version)
- if not release._metadata:
+ if not release.metadata:
location = release.get_distribution().unpack()
pkg_info = os.path.join(location, 'PKG-INFO')
- release._metadata = Metadata(pkg_info)
+ release.metadata = Metadata(pkg_info)
return release
def _switch_to_next_mirror(self):
@@ -213,7 +232,8 @@
"""
self._mirrors_used.add(self.index_url)
index_url = self._mirrors.pop()
- if not ("http://" or "https://" or "file://") in index_url:
+ # XXX use urlparse for a real check of missing scheme part
+ if not index_url.startswith(("http://", "https://", "file://")):
index_url = "http://%s" % index_url
if not index_url.endswith("/simple"):
@@ -264,9 +284,8 @@
name = release.name
else:
name = release_info['name']
- if not name.lower() in self._projects:
- self._projects[name.lower()] = ReleasesList(name,
- index=self._index)
+ if name.lower() not in self._projects:
+ self._projects[name.lower()] = ReleasesList(name, index=self._index)
if release:
self._projects[name.lower()].add_release(release=release)
@@ -292,27 +311,32 @@
method on it)
"""
f = self._open_url(url)
- base_url = f.url
- if url not in self._processed_urls:
- self._processed_urls.append(url)
- link_matcher = self._get_link_matcher(url)
- for link, is_download in link_matcher(f.read(), base_url):
- if link not in self._processed_urls:
- if self._is_distribution(link) or is_download:
- self._processed_urls.append(link)
- # it's a distribution, so create a dist object
- try:
- infos = get_infos_from_url(link, project_name,
- is_external=not self.index_url in url)
- except CantParseArchiveName, e:
- logger.warning(
- "version has not been parsed: %s", e)
+ try:
+ base_url = f.url
+ if url not in self._processed_urls:
+ self._processed_urls.append(url)
+ link_matcher = self._get_link_matcher(url)
+ for link, is_download in link_matcher(f.read().decode(), base_url):
+ if link not in self._processed_urls:
+ if self._is_distribution(link) or is_download:
+ self._processed_urls.append(link)
+ # it's a distribution, so create a dist object
+ try:
+ infos = get_infos_from_url(link, project_name,
+ is_external=self.index_url not in url)
+ except CantParseArchiveName:
+ e = sys.exc_info()[1]
+ if self.verbose:
+ logger.warning(
+ "version has not been parsed: %s", e)
+ else:
+ self._register_release(release_info=infos)
else:
- self._register_release(release_info=infos)
- else:
- if self._is_browsable(link) and follow_links:
- self._process_url(link, project_name,
- follow_links=False)
+ if self._is_browsable(link) and follow_links:
+ self._process_url(link, project_name,
+ follow_links=False)
+ finally:
+ f.close()
def _get_link_matcher(self, url):
"""Returns the right link matcher function of the given url
@@ -331,6 +355,9 @@
This matches the simple index requirements for matching links.
If follow_externals is set to False, dont yeld the external
urls.
+
+ :param content: the content of the page we want to parse
+ :param base_url: the url of this page.
"""
for match in HREF.finditer(content):
url = self._get_full_url(match.group(1), base_url)
@@ -340,7 +367,7 @@
for match in REL.finditer(content):
# search for rel links.
tag, rel = match.groups()
- rels = map(str.strip, rel.lower().split(','))
+ rels = [s.strip() for s in rel.lower().split(',')]
if 'homepage' in rels or 'download' in rels:
for match in HREF.finditer(tag):
url = self._get_full_url(match.group(1), base_url)
@@ -363,7 +390,11 @@
:param name: the name of the project to find the page
"""
# Browse and index the content of the given PyPI page.
- url = self.index_url + name + "/"
+ if self.scheme == 'file':
+ ender = os.path.sep
+ else:
+ ender = '/'
+ url = self.index_url + name + ender
self._process_url(url, name)
@socket_timeout()
@@ -376,19 +407,19 @@
# authentication stuff
if scheme in ('http', 'https'):
- auth, host = urllib2.splituser(netloc)
+ auth, host = urlparse.splituser(netloc)
else:
auth = None
# add index.html automatically for filesystem paths
if scheme == 'file':
- if url.endswith('/'):
+ if url.endswith(os.path.sep):
url += "index.html"
# add authorization headers if auth is provided
if auth:
auth = "Basic " + \
- urllib2.unquote(auth).encode('base64').strip()
+ urlparse.unquote(auth).encode('base64').strip()
new_url = urlparse.urlunparse((
scheme, host, path, params, query, frag))
request = urllib2.Request(new_url)
@@ -398,17 +429,21 @@
request.add_header('User-Agent', USER_AGENT)
try:
fp = urllib2.urlopen(request)
- except (ValueError, httplib.InvalidURL), v:
+ except (ValueError, httplib.InvalidURL):
+ v = sys.exc_info()[1]
msg = ' '.join([str(arg) for arg in v.args])
- raise DistutilsIndexError('%s %s' % (url, msg))
- except urllib2.HTTPError, v:
- return v
- except urllib2.URLError, v:
+ raise PackagingPyPIError('%s %s' % (url, msg))
+ except urllib2.HTTPError:
+ return sys.exc_info()[1]
+ except urllib2.URLError:
+ v = sys.exc_info()[1]
raise DownloadError("Download error for %s: %s" % (url, v.reason))
- except httplib.BadStatusLine, v:
+ except httplib.BadStatusLine:
+ v = sys.exc_info()[1]
raise DownloadError('%s returned a bad status line. '
'The server might be down, %s' % (url, v.line))
- except httplib.HTTPException, v:
+ except httplib.HTTPException:
+ v = sys.exc_info()[1]
raise DownloadError("Download error for %s: %s" % (url, v))
except socket.timeout:
raise DownloadError("The server timeouted")
@@ -430,9 +465,9 @@
elif what.startswith('#'):
what = int(what[1:])
else:
- from htmlentitydefs import name2codepoint
+ from html.entities import name2codepoint
what = name2codepoint.get(what, match.group(0))
- return unichr(what)
+ return chr(what)
def _htmldecode(self, text):
"""Decode HTML entities in the given text."""
diff --git a/distutils2/pypi/wrapper.py b/distutils2/pypi/wrapper.py
--- a/distutils2/pypi/wrapper.py
+++ b/distutils2/pypi/wrapper.py
@@ -1,4 +1,10 @@
-from distutils2.index import simple, xmlrpc
+"""Convenient client for all PyPI APIs.
+
+This module provides a ClientWrapper class which will use the "simple"
+or XML-RPC API to request information or files from an index.
+"""
+
+from distutils2.pypi import simple, xmlrpc
_WRAPPER_MAPPINGS = {'get_release': 'simple',
'get_releases': 'simple',
@@ -19,14 +25,14 @@
exception = None
methods = [func]
for f in wrapper._indexes.values():
- if f != func.im_self and hasattr(f, func.__name__):
+ if f != func.__self__ and hasattr(f, func.__name__):
methods.append(getattr(f, func.__name__))
for method in methods:
try:
response = method(*args, **kwargs)
retry = False
- except Exception, e:
- exception = e
+ except Exception:
+ exception = sys.exc_info()[1]
if not retry:
break
if retry and exception:
@@ -43,7 +49,7 @@
mappings.
If one of the indexes returns an error, tries to use others indexes.
- :param index: tell wich index to rely on by default.
+ :param index: tell which index to rely on by default.
:param index_classes: a dict of name:class to use as indexes.
:param indexes: a dict of name:index already instantiated
:param mappings: the mappings to use for this wrapper
@@ -58,7 +64,7 @@
# instantiate the classes and set their _project attribute to the one
# of the wrapper.
- for name, cls in index_classes.iteritems():
+ for name, cls in index_classes.items():
obj = self._indexes.setdefault(name, cls())
obj._projects = self._projects
obj._index = self
diff --git a/distutils2/pypi/xmlrpc.py b/distutils2/pypi/xmlrpc.py
--- a/distutils2/pypi/xmlrpc.py
+++ b/distutils2/pypi/xmlrpc.py
@@ -1,12 +1,20 @@
-import logging
-import xmlrpclib
+"""Spider using the XML-RPC PyPI API.
+This module contains the class Client, a spider that can be used to find
+and retrieve distributions from a project index (like the Python Package
+Index), using its XML-RPC API (see documentation of the reference
+implementation at http://wiki.python.org/moin/PyPiXmlRpc).
+"""
+
+import xmlrpclib, sys
+
+from distutils2 import logger
from distutils2.errors import IrrationalVersionError
-from distutils2.index.base import BaseClient
-from distutils2.index.errors import (ProjectNotFound, InvalidSearchField,
- ReleaseNotFound)
-from distutils2.index.dist import ReleaseInfo
from distutils2.version import get_version_predicate
+from distutils2.pypi.base import BaseClient
+from distutils2.pypi.errors import (ProjectNotFound, InvalidSearchField,
+ ReleaseNotFound)
+from distutils2.pypi.dist import ReleaseInfo
__all__ = ['Client', 'DEFAULT_XMLRPC_INDEX_URL']
@@ -23,11 +31,11 @@
If no server_url is specified, use the default PyPI XML-RPC URL,
defined in the DEFAULT_XMLRPC_INDEX_URL constant::
- >>> client = XMLRPCClient()
+ >>> client = Client()
>>> client.server_url == DEFAULT_XMLRPC_INDEX_URL
True
- >>> client = XMLRPCClient("http://someurl/")
+ >>> client = Client("http://someurl/")
>>> client.server_url
'http://someurl/'
"""
@@ -46,8 +54,8 @@
predicate = get_version_predicate(requirements)
releases = self.get_releases(predicate.name)
release = releases.get_last(predicate, prefer_final)
- self.get_metadata(release.name, "%s" % release.version)
- self.get_distributions(release.name, "%s" % release.version)
+ self.get_metadata(release.name, str(release.version))
+ self.get_distributions(release.name, str(release.version))
return release
def get_releases(self, requirements, prefer_final=None, show_hidden=True,
@@ -61,7 +69,7 @@
informations (eg. make a new XML-RPC call).
::
- >>> client = XMLRPCClient()
+ >>> client = Client()
>>> client.get_releases('Foo')
['1.1', '1.2', '1.3']
@@ -84,8 +92,7 @@
# list of releases that does not contains hidden ones
all_versions = get_versions(project_name, show_hidden)
existing_versions = project.get_versions()
- hidden_versions = list(set(all_versions) -
- set(existing_versions))
+ hidden_versions = set(all_versions) - set(existing_versions)
for version in hidden_versions:
project.add_release(release=ReleaseInfo(project_name,
version, index=self._index))
@@ -103,6 +110,7 @@
project.sort_releases(prefer_final)
return project
+
def get_distributions(self, project_name, version):
"""Grab informations about distributions from XML-RPC.
@@ -163,8 +171,9 @@
project.add_release(release=ReleaseInfo(p['name'],
p['version'], metadata={'summary': p['summary']},
index=self._index))
- except IrrationalVersionError, e:
- logging.warn("Irrational version error found: %s", e)
+ except IrrationalVersionError:
+ e = sys.exc_info()[1]
+ logger.warning("Irrational version error found: %s", e)
return [self._projects[p['name'].lower()] for p in projects]
def get_all_projects(self):
@@ -181,7 +190,7 @@
If no server proxy is defined yet, creates a new one::
- >>> client = XmlRpcClient()
+ >>> client = Client()
>>> client.proxy()
<ServerProxy for python.org/pypi>
diff --git a/distutils2/resources.py b/distutils2/resources.py
deleted file mode 100644
--- a/distutils2/resources.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import os
-
-from distutils2.util import iglob
-
-
-def _rel_path(base, path):
- assert path.startswith(base)
- return path[len(base):].lstrip('/')
-
-
-def resources_dests(resources_root, rules):
- """find destination of resources files"""
- 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 = _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 = _rel_path(abs_base, abs_path)
- destinations[resource_file] = os.path.join(dest, rel_path)
- return destinations
diff --git a/distutils2/run.py b/distutils2/run.py
--- a/distutils2/run.py
+++ b/distutils2/run.py
@@ -1,35 +1,26 @@
-"""distutils2.dispatcher
+"""Main command line parser. Implements the pysetup script."""
-Parses the command line.
-"""
+import os
+import re
+import sys
+import getopt
import logging
-import re
-import os
-import sys
-
-from distutils2.errors import DistutilsError, CCompilerError
-from distutils2._backport.pkgutil import get_distributions, get_distribution
-from distutils2.depgraph import generate_graph
-from distutils2.install import install, remove
-from distutils2.dist import Distribution
-
-from distutils2.command import get_command_class, STANDARD_COMMANDS
-
-from distutils2.errors import (DistutilsOptionError, DistutilsArgError,
- DistutilsModuleError, DistutilsClassError)
from distutils2 import logger
+from distutils2.dist import Distribution
+from distutils2.util import _is_archive_file, generate_setup_py
+from distutils2.command import get_command_class, STANDARD_COMMANDS
+from distutils2.install import install, install_local_project, remove
+from distutils2.database import get_distribution, get_distributions
+from distutils2.depgraph import generate_graph
from distutils2.fancy_getopt import FancyGetopt
+from distutils2.errors import (PackagingArgError, PackagingError,
+ PackagingModuleError, PackagingClassError,
+ CCompilerError)
+
command_re = re.compile(r'^[a-zA-Z]([a-zA-Z0-9_]*)$')
-run_usage = """\
-usage: pysetup run [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
- or: pysetup run --help
- or: pysetup run --list-commands
- or: pysetup run cmd --help
-"""
-
common_usage = """\
Actions:
%(actions)s
@@ -39,77 +30,252 @@
pysetup action --help
"""
-global_options = [('verbose', 'v', "run verbosely (default)", 1),
- ('quiet', 'q', "run quietly (turns verbosity off)"),
- ('dry-run', 'n', "don't actually do anything"),
- ('help', 'h', "show detailed help message"),
- ('no-user-cfg', None,
- 'ignore pydistutils.cfg in your home directory'),
- ('version', None, 'Display the version'),
- ]
+create_usage = """\
+Usage: pysetup create
+ or: pysetup create --help
+
+Create a new Python project.
+"""
+
+generate_usage = """\
+Usage: pysetup generate-setup
+ or: pysetup generate-setup --help
+
+Generate a setup.py script for backward-compatibility purposes.
+"""
+
+
+graph_usage = """\
+Usage: pysetup graph dist
+ or: pysetup graph --help
+
+Print dependency graph for the distribution.
+
+positional arguments:
+ dist installed distribution name
+"""
+
+install_usage = """\
+Usage: pysetup install [dist]
+ or: pysetup install [archive]
+ or: pysetup install [src_dir]
+ or: pysetup install --help
+
+Install a Python distribution from the indexes, source directory, or sdist.
+
+positional arguments:
+ archive path to source distribution (zip, tar.gz)
+ dist distribution name to install from the indexes
+ scr_dir path to source directory
+
+"""
+
+metadata_usage = """\
+Usage: pysetup metadata [dist] [-f field ...]
+ or: pysetup metadata [dist] [--all]
+ or: pysetup metadata --help
+
+Print metadata for the distribution.
+
+positional arguments:
+ dist installed distribution name
+
+optional arguments:
+ -f metadata field to print
+ --all print all metadata fields
+"""
+
+remove_usage = """\
+Usage: pysetup remove dist [-y]
+ or: pysetup remove --help
+
+Uninstall a Python distribution.
+
+positional arguments:
+ dist installed distribution name
+
+optional arguments:
+ -y auto confirm distribution removal
+"""
+
+run_usage = """\
+Usage: pysetup run [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
+ or: pysetup run --help
+ or: pysetup run --list-commands
+ or: pysetup run cmd --help
+"""
+
+list_usage = """\
+Usage: pysetup list dist [dist ...]
+ or: pysetup list --help
+ or: pysetup list --all
+
+Print name, version and location for the matching installed distributions.
+
+positional arguments:
+ dist installed distribution name
+
+optional arguments:
+ --all list all installed distributions
+"""
+
+search_usage = """\
+Usage: pysetup search [project] [--simple [url]] [--xmlrpc [url] [--fieldname value ...] --operator or|and]
+ or: pysetup search --help
+
+Search the indexes for the matching projects.
+
+positional arguments:
+ project the project pattern to search for
+
+optional arguments:
+ --xmlrpc [url] wether to use the xmlrpc index or not. If an url is
+ specified, it will be used rather than the default one.
+
+ --simple [url] wether to use the simple index or not. If an url is
+ specified, it will be used rather than the default one.
+
+ --fieldname value Make a search on this field. Can only be used if
+ --xmlrpc has been selected or is the default index.
+
+ --operator or|and Defines what is the operator to use when doing xmlrpc
+ searchs with multiple fieldnames. Can only be used if
+ --xmlrpc has been selected or is the default index.
+"""
+
+global_options = [
+ # The fourth entry for verbose means that it can be repeated.
+ ('verbose', 'v', "run verbosely (default)", True),
+ ('quiet', 'q', "run quietly (turns verbosity off)"),
+ ('dry-run', 'n', "don't actually do anything"),
+ ('help', 'h', "show detailed help message"),
+ ('no-user-cfg', None, 'ignore pydistutils.cfg in your home directory'),
+ ('version', None, 'Display the version'),
+]
+
+negative_opt = {'quiet': 'verbose'}
display_options = [
- ('help-commands', None,
- "list all available commands"),
- ]
-
+ ('help-commands', None, "list all available commands"),
+]
display_option_names = [x[0].replace('-', '_') for x in display_options]
-negative_opt = {'quiet': 'verbose'}
-def _set_logger():
- logger.setLevel(logging.INFO)
- sth = logging.StreamHandler(sys.stderr)
- sth.setLevel(logging.INFO)
- logger.addHandler(sth)
- logger.propagate = 0
+def _parse_args(args, options, long_options):
+ """Transform sys.argv input into a dict.
+
+ :param args: the args to parse (i.e sys.argv)
+ :param options: the list of options to pass to getopt
+ :param long_options: the list of string with the names of the long options
+ to be passed to getopt.
+
+ The function returns a dict with options/long_options as keys and matching
+ values as values.
+ """
+ optlist, args = getopt.gnu_getopt(args, options, long_options)
+ optdict = {}
+ optdict['args'] = args
+ for k, v in optlist:
+ k = k.lstrip('-')
+ if k not in optdict:
+ optdict[k] = []
+ if v:
+ optdict[k].append(v)
+ else:
+ optdict[k].append(v)
+ return optdict
+
+
+class action_help(object):
+ """Prints a help message when the standard help flags: -h and --help
+ are used on the commandline.
+ """
+
+ def __init__(self, help_msg):
+ self.help_msg = help_msg
+
+ def __call__(self, f):
+ def wrapper(*args, **kwargs):
+ f_args = args[1]
+ if '--help' in f_args or '-h' in f_args:
+ print(self.help_msg)
+ return
+ return f(*args, **kwargs)
+ return wrapper
+
+
+@action_help(create_usage)
+def _create(distpatcher, args, **kw):
+ from distutils2.create import main
+ return main()
+
+
+@action_help(generate_usage)
+def _generate(distpatcher, args, **kw):
+ generate_setup_py()
+ logger.info('The setup.py was generated')
+
+
+@action_help(graph_usage)
def _graph(dispatcher, args, **kw):
- # XXX
- dists = get_distributions(use_egg_info=True)
- graph = generate_graph(dists)
- print(graph)
- return 0
-
-
- name = args[0]
+ name = args[1]
dist = get_distribution(name, use_egg_info=True)
if dist is None:
- print('Distribution not found.')
+ logger.warning('Distribution not found.')
+ return 1
else:
dists = get_distributions(use_egg_info=True)
graph = generate_graph(dists)
print(graph.repr_node(dist))
- return 0
+@action_help(install_usage)
+def _install(dispatcher, args, **kw):
+ # first check if we are in a source directory
+ if len(args) < 2:
+ # are we inside a project dir?
+ if os.path.isfile('setup.cfg') or os.path.isfile('setup.py'):
+ args.insert(1, os.getcwd())
+ else:
+ logger.warning('No project to install.')
+ return 1
+ target = args[1]
+ # installing from a source dir or archive file?
+ if os.path.isdir(target) or _is_archive_file(target):
+ return not install_local_project(target)
+ else:
+ # download from PyPI
+ return not install(target)
-def _search(dispatcher, args, **kw):
- search = args[0].lower()
- for dist in get_distributions(use_egg_info=True):
- name = dist.name.lower()
- if search in name:
- print('%s %s at %s' % (dist.name, dist.metadata['version'],
- dist.path))
- return 0
+@action_help(metadata_usage)
+def _metadata(dispatcher, args, **kw):
+ opts = _parse_args(args[1:], 'f:', ['all'])
+ if opts['args']:
+ name = opts['args'][0]
+ dist = get_distribution(name, use_egg_info=True)
+ if dist is None:
+ logger.warning('%r not installed', name)
+ return 1
+ elif os.path.isfile('setup.cfg'):
+ logger.info('searching local dir for metadata')
+ dist = Distribution() # XXX use config module
+ dist.parse_config_files()
+ else:
+ logger.warning('no argument given and no local setup.cfg found')
+ return 1
-
-def _metadata(dispatcher, args, **kw):
- ### XXX Needs to work on any installed package as well
- from distutils2.dist import Distribution
- dist = Distribution()
- dist.parse_config_files()
metadata = dist.metadata
- if 'all' in args:
+ if 'all' in opts:
keys = metadata.keys()
else:
- keys = args
- if len(keys) == 1:
- print metadata[keys[0]]
- return
+ if 'f' in opts:
+ keys = (k for k in opts['f'] if k in metadata)
+ else:
+ keys = ()
for key in keys:
if key in metadata:
@@ -117,26 +283,40 @@
value = metadata[key]
if isinstance(value, list):
for v in value:
- print(' ' + v)
+ print(' ', v)
else:
- print(' ' + value.replace('\n', '\n '))
- return 0
+ print(' ', value.replace('\n', '\n '))
+
+@action_help(remove_usage)
+def _remove(distpatcher, args, **kw):
+ opts = _parse_args(args[1:], 'y', [])
+ if 'y' in opts:
+ auto_confirm = True
+ else:
+ auto_confirm = False
+
+ retcode = 0
+ for dist in set(opts['args']):
+ try:
+ remove(dist, auto_confirm=auto_confirm)
+ except PackagingError:
+ logger.warning('%r not installed', dist)
+ retcode = 1
+
+ return retcode
+
+
+@action_help(run_usage)
def _run(dispatcher, args, **kw):
parser = dispatcher.parser
args = args[1:]
commands = STANDARD_COMMANDS # + extra commands
- # do we have a global option ?
- if args in (['--help'], []):
- print(run_usage)
- return
-
if args == ['--list-commands']:
print('List of available commands:')
- cmds = list(commands)
- cmds.sort()
+ cmds = sorted(commands)
for cmd in cmds:
cls = dispatcher.cmdclass.get(cmd) or get_command_class(cmd)
@@ -160,53 +340,63 @@
# XXX still need to be extracted from Distribution
dist.parse_config_files()
-
- try:
- for cmd in dispatcher.commands:
- dist.run_command(cmd, dispatcher.command_options[cmd])
-
- except KeyboardInterrupt:
- raise SystemExit("interrupted")
- except (IOError, os.error, DistutilsError, CCompilerError), msg:
- raise SystemExit("error: " + str(msg))
+ for cmd in dispatcher.commands:
+ dist.run_command(cmd, dispatcher.command_options[cmd])
# XXX this is crappy
return dist
-def _install(dispatcher, args, **kw):
- install(args[0])
- return 0
-def _remove(distpatcher, args, **kw):
- remove(options.remove)
- return 0
+@action_help(list_usage)
+def _list(dispatcher, args, **kw):
+ opts = _parse_args(args[1:], '', ['all'])
+ dists = get_distributions(use_egg_info=True)
+ if 'all' in opts or opts['args'] == []:
+ results = dists
+ listall = True
+ else:
+ results = (d for d in dists if d.name.lower() in opts['args'])
+ listall = False
-def _create(distpatcher, args, **kw):
- from distutils2.mkcfg import main
- main()
- return 0
+ number = 0
+ for dist in results:
+ print('%r %s (from %r)' % (dist.name, dist.version, dist.path))
+ number += 1
+ if number == 0:
+ if listall:
+ logger.info('Nothing seems to be installed.')
+ else:
+ logger.warning('No matching distribution found.')
+ return 1
+ else:
+ logger.info('Found %d projects installed.', number)
-actions = [('run', 'Run one or several commands', _run),
- ('metadata', 'Display the metadata of a project', _metadata),
- ('install', 'Install a project', _install),
- ('remove', 'Remove a project', _remove),
- ('search', 'Search for a project', _search),
- ('graph', 'Display a graph', _graph),
- ('create', 'Create a Project', _create),]
+@action_help(search_usage)
+def _search(dispatcher, args, **kw):
+ """The search action.
+ It is able to search for a specific index (specified with --index), using
+ the simple or xmlrpc index types (with --type xmlrpc / --type simple)
+ """
+ #opts = _parse_args(args[1:], '', ['simple', 'xmlrpc'])
+ # 1. what kind of index is requested ? (xmlrpc / simple)
+ logger.error('not implemented')
+ return 1
-def fix_help_options(options):
- """Convert a 4-tuple 'help_options' list as found in various command
- classes to the 3-tuple form required by FancyGetopt.
- """
- new_options = []
- for help_tuple in options:
- new_options.append(help_tuple[0:3])
- return new_options
-
+actions = [
+ ('run', 'Run one or several commands', _run),
+ ('metadata', 'Display the metadata of a project', _metadata),
+ ('install', 'Install a project', _install),
+ ('remove', 'Remove a project', _remove),
+ ('search', 'Search for a project in the indexes', _search),
+ ('list', 'List installed releases', _list),
+ ('graph', 'Display a graph', _graph),
+ ('create', 'Create a project', _create),
+ ('generate-setup', 'Generate a backward-comptatible setup.py', _generate),
+]
class Dispatcher(object):
@@ -214,22 +404,21 @@
"""
def __init__(self, args=None):
self.verbose = 1
- self.dry_run = 0
- self.help = 0
- self.script_name = 'pysetup'
+ self.dry_run = False
+ self.help = False
self.cmdclass = {}
self.commands = []
self.command_options = {}
for attr in display_option_names:
- setattr(self, attr, 0)
+ setattr(self, attr, False)
self.parser = FancyGetopt(global_options + display_options)
self.parser.set_negative_aliases(negative_opt)
+ # FIXME this parses everything, including command options (e.g. "run
+ # build -i" errors with "option -i not recognized")
args = self.parser.getopt(args=args, object=self)
- #args = args[1:]
-
# if first arg is "run", we have some commands
if len(args) == 0:
self.action = None
@@ -239,31 +428,44 @@
allowed = [action[0] for action in actions] + [None]
if self.action not in allowed:
msg = 'Unrecognized action "%s"' % self.action
- raise DistutilsArgError(msg)
+ raise PackagingArgError(msg)
- # setting up the logger
- handler = logging.StreamHandler()
- logger.addHandler(handler)
-
- if self.verbose:
- handler.setLevel(logging.DEBUG)
- else:
- handler.setLevel(logging.INFO)
+ self._set_logger()
+ self.args = args
# for display options we return immediately
- option_order = self.parser.get_option_order()
-
- self.args = args
-
if self.help or self.action is None:
self._show_help(self.parser, display_options_=False)
- return
+
+ def _set_logger(self):
+ # setting up the logging level from the command-line options
+ # -q gets warning, error and critical
+ if self.verbose == 0:
+ level = logging.WARNING
+ # default level or -v gets info too
+ # XXX there's a bug somewhere: the help text says that -v is default
+ # (and verbose is set to 1 above), but when the user explicitly gives
+ # -v on the command line, self.verbose is incremented to 2! Here we
+ # compensate for that (I tested manually). On a related note, I think
+ # it's a good thing to use -q/nothing/-v/-vv on the command line
+ # instead of logging constants; it will be easy to add support for
+ # logging configuration in setup.cfg for advanced users. --merwok
+ elif self.verbose in (1, 2):
+ level = logging.INFO
+ else: # -vv and more for debug
+ level = logging.DEBUG
+
+ # setting up the stream handler
+ handler = logging.StreamHandler(sys.stderr)
+ handler.setLevel(level)
+ logger.addHandler(handler)
+ logger.setLevel(level)
def _parse_command_opts(self, parser, args):
# Pull the current command from the head of the command line
command = args[0]
if not command_re.match(command):
- raise SystemExit("invalid command name %r" % command)
+ raise SystemExit("invalid command name %r" % (command,))
self.commands.append(command)
# Dig up the command class that implements this command, so we
@@ -271,24 +473,24 @@
# it takes.
try:
cmd_class = get_command_class(command)
- except DistutilsModuleError, msg:
- raise DistutilsArgError(msg)
+ except PackagingModuleError:
+ raise PackagingArgError(sys.exc_info()[1])
- # XXX We want to push this in distutils.command
+ # XXX We want to push this in distutils2.command
#
# Require that the command class be derived from Command -- want
# to be sure that the basic "command" interface is implemented.
for meth in ('initialize_options', 'finalize_options', 'run'):
if hasattr(cmd_class, meth):
continue
- raise DistutilsClassError(
+ raise PackagingClassError(
'command %r must implement %r' % (cmd_class, meth))
# Also make sure that the command object provides a list of its
# known options.
if not (hasattr(cmd_class, 'user_options') and
isinstance(cmd_class.user_options, list)):
- raise DistutilsClassError(
+ raise PackagingClassError(
"command class %s must provide "
"'user_options' attribute (a list of tuples)" % cmd_class)
@@ -303,7 +505,7 @@
# format (tuple of four) so we need to preprocess them here.
if (hasattr(cmd_class, 'help_options') and
isinstance(cmd_class.help_options, list)):
- help_options = fix_help_options(cmd_class.help_options)
+ help_options = cmd_class.help_options[:]
else:
help_options = []
@@ -321,14 +523,14 @@
if (hasattr(cmd_class, 'help_options') and
isinstance(cmd_class.help_options, list)):
- help_option_found = 0
- for (help_option, short, desc, func) in cmd_class.help_options:
+ help_option_found = False
+ for help_option, short, desc, func in cmd_class.help_options:
if hasattr(opts, help_option.replace('-', '_')):
- help_option_found = 1
+ help_option_found = True
if hasattr(func, '__call__'):
func()
else:
- raise DistutilsClassError(
+ raise PackagingClassError(
"invalid help function %r for help option %r: "
"must be a callable object (function, etc.)"
% (func, help_option))
@@ -339,7 +541,7 @@
# Put the options from the command line into their official
# holding pen, the 'command_options' dictionary.
opt_dict = self.get_option_dict(command)
- for (name, value) in vars(opts).iteritems():
+ for name, value in vars(opts).items():
opt_dict[name] = ("command line", value)
return args
@@ -366,23 +568,23 @@
parser.print_help(usage + "\nGlobal options:")
- def _show_help(self, parser, global_options_=1, display_options_=1,
+ def _show_help(self, parser, global_options_=True, display_options_=True,
commands=[]):
# late import because of mutual dependence between these modules
from distutils2.command.cmd import Command
print('Usage: pysetup [options] action [action_options]')
- print('')
+ print()
if global_options_:
self.print_usage(self.parser)
- print('')
+ print()
if display_options_:
parser.set_option_table(display_options)
parser.print_help(
"Information display options (just display " +
"information, ignore any commands)")
- print('')
+ print()
for command in commands:
if isinstance(command, type) and issubclass(command, Command):
@@ -391,35 +593,30 @@
cls = get_command_class(command)
if (hasattr(cls, 'help_options') and
isinstance(cls.help_options, list)):
- parser.set_option_table(cls.user_options +
- fix_help_options(cls.help_options))
+ parser.set_option_table(cls.user_options + cls.help_options)
else:
parser.set_option_table(cls.user_options)
-
parser.print_help("Options for %r command:" % cls.__name__)
- print('')
+ print()
def _show_command_help(self, command):
- from distutils2.command.cmd import Command
- if isinstance(command, str):
+ if isinstance(command, basestring):
command = get_command_class(command)
- name = command.get_command_name()
-
desc = getattr(command, 'description', '(no description available)')
- print('Description: %s' % desc)
- print('')
+ print('Description:', desc)
+ print()
if (hasattr(command, 'help_options') and
isinstance(command.help_options, list)):
self.parser.set_option_table(command.user_options +
- fix_help_options(command.help_options))
+ command.help_options)
else:
self.parser.set_option_table(command.user_options)
self.parser.print_help("Options:")
- print('')
+ print()
def _get_command_groups(self):
"""Helper function to retrieve all the command class names divided
@@ -440,21 +637,16 @@
'description'.
"""
std_commands, extra_commands = self._get_command_groups()
- max_length = 0
- for cmd in list(std_commands) + list(extra_commands):
- if len(cmd) > max_length:
- max_length = len(cmd)
+ max_length = max(len(command)
+ for commands in (std_commands, extra_commands)
+ for command in commands)
- self.print_command_list(std_commands,
- "Standard commands",
- max_length)
+ self.print_command_list(std_commands, "Standard commands", max_length)
if extra_commands:
- print
- self.print_command_list(extra_commands,
- "Extra commands",
+ print()
+ self.print_command_list(extra_commands, "Extra commands",
max_length)
-
def print_command_list(self, commands, header, max_length):
"""Print a subset of the list of all commands -- used by
'print_commands()'.
@@ -468,10 +660,10 @@
print(" %-*s %s" % (max_length, cmd, description))
-
def __call__(self):
if self.action is None:
- return 0
+ return
+
for action, desc, func in actions:
if action == self.action:
return func(self, self.args)
@@ -479,11 +671,23 @@
def main(args=None):
- dispatcher = Dispatcher(args)
- if dispatcher.action is None:
- return 0
+ old_level = logger.level
+ old_handlers = list(logger.handlers)
+ try:
+ dispatcher = Dispatcher(args)
+ if dispatcher.action is None:
+ return
+ return dispatcher()
+ except KeyboardInterrupt:
+ logger.info('interrupted')
+ return 1
+ except (IOError, os.error, PackagingError, CCompilerError):
+ logger.exception(sys.exc_info()[1])
+ return 1
+ finally:
+ logger.setLevel(old_level)
+ logger.handlers[:] = old_handlers
- return dispatcher()
if __name__ == '__main__':
sys.exit(main())
diff --git a/distutils2/tests/__init__.py b/distutils2/tests/__init__.py
--- a/distutils2/tests/__init__.py
+++ b/distutils2/tests/__init__.py
@@ -5,39 +5,23 @@
'test' and contains a function test_suite(). The function is expected
to return an initialized unittest.TestSuite instance.
-Tests for the command classes in the distutils2.command package are
-included in distutils2.tests as well, instead of using a separate
-distutils2.command.tests package, since command identification is done
-by import rather than matching pre-defined names.
+Utility code is included in distutils2.tests.support.
+"""
-Always import unittest from this module, it will be the right version
-(standard library unittest for 3.2 and higher, third-party unittest2
-release for older versions).
-
-Utility code is included in distutils2.tests.support.
-"""
+# Put this text back for the backport
+#Always import unittest from this module, it will be the right version
+#(standard library unittest for 3.2 and higher, third-party unittest2
+#elease for older versions).
import os
import sys
+import unittest2 as unittest
+from .support import TESTFN
-if sys.version_info >= (3, 2):
- # improved unittest package from 3.2's standard library
- import unittest
-else:
- try:
- # external release of same package for older versions
- import unittest2 as unittest
- except ImportError:
- sys.exit('Error: You have to install unittest2')
-
-# use TESTFN from stdlib, pull in unlink for other modules to use as well
-if sys.version_info[0] == 3:
- from test.support import TESTFN, unlink
-else :
- from test.test_support import TESTFN, unlink
+# XXX move helpers to support, add tests for them, remove things that
+# duplicate test.support (or keep them for the backport; needs thinking)
here = os.path.dirname(__file__) or os.curdir
-
verbose = 1
def test_suite():
@@ -50,6 +34,7 @@
suite.addTest(module.test_suite())
return suite
+
class Error(Exception):
"""Base class for regression test exceptions."""
@@ -88,12 +73,13 @@
def run_unittest(classes, verbose_=1):
"""Run tests from unittest.TestCase-derived classes.
- Extracted from stdlib test.test_support and modified to support unittest.
+ Originally extracted from stdlib test.test_support and modified to
+ support unittest2.
"""
valid_types = (unittest.TestSuite, unittest.TestCase)
suite = unittest.TestSuite()
for cls in classes:
- if isinstance(cls, str):
+ if isinstance(cls, basestring):
if cls in sys.modules:
suite.addTest(unittest.findTestCases(sys.modules[cls]))
else:
@@ -111,7 +97,7 @@
stick around to hog resources and create problems when looking
for refleaks.
- Extracted from stdlib test.test_support.
+ Extracted from stdlib test.support.
"""
# Reap all our dead child processes so we don't leave zombies around.
@@ -127,10 +113,11 @@
except:
break
+
def captured_stdout(func, *args, **kw):
- import StringIO
+ from StringIO import StringIO
orig_stdout = getattr(sys, 'stdout')
- setattr(sys, 'stdout', StringIO.StringIO())
+ setattr(sys, 'stdout', StringIO())
try:
res = func(*args, **kw)
sys.stdout.seek(0)
@@ -138,12 +125,9 @@
finally:
setattr(sys, 'stdout', orig_stdout)
+
def unload(name):
try:
del sys.modules[name]
except KeyError:
pass
-
-
-if __name__ == "__main__":
- unittest.main(defaultTest="test_suite")
diff --git a/distutils2/tests/__main__.py b/distutils2/tests/__main__.py
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/__main__.py
@@ -0,0 +1,23 @@
+"""Packaging test suite runner."""
+
+# Ripped from importlib tests, thanks Brett!
+
+import os
+import sys
+import unittest2
+from .support import run_unittest, reap_children, reap_threads
+
+
+@reap_threads
+def test_main():
+ try:
+ start_dir = os.path.dirname(__file__)
+ top_dir = os.path.dirname(os.path.dirname(start_dir))
+ test_loader = unittest2.TestLoader()
+ run_unittest(test_loader.discover(start_dir, top_level_dir=top_dir))
+ finally:
+ reap_children()
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/distutils2/tests/fake_dists/babar-0.1.dist-info/INSTALLER b/distutils2/tests/fake_dists/babar-0.1.dist-info/INSTALLER
new file mode 100644
diff --git a/distutils2/tests/fake_dists/babar-0.1.dist-info/METADATA b/distutils2/tests/fake_dists/babar-0.1.dist-info/METADATA
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/babar-0.1.dist-info/METADATA
@@ -0,0 +1,4 @@
+Metadata-version: 1.2
+Name: babar
+Version: 0.1
+Author: FELD Boris
\ No newline at end of file
diff --git a/distutils2/tests/fake_dists/babar-0.1.dist-info/RECORD b/distutils2/tests/fake_dists/babar-0.1.dist-info/RECORD
new file mode 100644
diff --git a/distutils2/tests/fake_dists/babar-0.1.dist-info/REQUESTED b/distutils2/tests/fake_dists/babar-0.1.dist-info/REQUESTED
new file mode 100644
diff --git a/distutils2/tests/fake_dists/babar-0.1.dist-info/RESOURCES b/distutils2/tests/fake_dists/babar-0.1.dist-info/RESOURCES
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/babar-0.1.dist-info/RESOURCES
@@ -0,0 +1,2 @@
+babar.png,babar.png
+babar.cfg,babar.cfg
\ No newline at end of file
diff --git a/distutils2/tests/fake_dists/babar.cfg b/distutils2/tests/fake_dists/babar.cfg
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/babar.cfg
@@ -0,0 +1,1 @@
+Config
\ No newline at end of file
diff --git a/distutils2/tests/fake_dists/babar.png b/distutils2/tests/fake_dists/babar.png
new file mode 100644
diff --git a/distutils2/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO b/distutils2/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/bacon-0.1.egg-info/PKG-INFO
@@ -0,0 +1,6 @@
+Metadata-Version: 1.2
+Name: bacon
+Version: 0.1
+Provides-Dist: truffles (2.0)
+Provides-Dist: bacon (0.1)
+Obsoletes-Dist: truffles (>=0.9,<=1.5)
diff --git a/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO b/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/PKG-INFO
@@ -0,0 +1,18 @@
+Metadata-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(a)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
diff --git a/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt b/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/SOURCES.txt
new file mode 100644
diff --git a/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt b/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/dependency_links.txt
@@ -0,0 +1,1 @@
+
diff --git a/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt b/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/entry_points.txt
@@ -0,0 +1,3 @@
+
+ # -*- Entry points: -*-
+
\ No newline at end of file
diff --git a/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe b/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/not-zip-safe
@@ -0,0 +1,1 @@
+
diff --git a/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt b/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/requires.txt
@@ -0,0 +1,6 @@
+# this should be ignored
+
+strawberry >=0.5
+
+[section ignored]
+foo ==0.5
diff --git a/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt b/distutils2/tests/fake_dists/banana-0.4.egg/EGG-INFO/top_level.txt
new file mode 100644
diff --git a/distutils2/tests/fake_dists/cheese-2.0.2.egg-info b/distutils2/tests/fake_dists/cheese-2.0.2.egg-info
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/cheese-2.0.2.egg-info
@@ -0,0 +1,5 @@
+Metadata-Version: 1.2
+Name: cheese
+Version: 2.0.2
+Provides-Dist: truffles (1.0.2)
+Obsoletes-Dist: truffles (!=1.2,<=2.0)
diff --git a/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER b/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/INSTALLER
new file mode 100644
diff --git a/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA b/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/METADATA
@@ -0,0 +1,9 @@
+Metadata-Version: 1.2
+Name: choxie
+Version: 2.0.0.9
+Summary: Chocolate with a 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)
diff --git a/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD b/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/RECORD
new file mode 100644
diff --git a/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED b/distutils2/tests/fake_dists/choxie-2.0.0.9.dist-info/REQUESTED
new file mode 100644
diff --git a/distutils2/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py b/distutils2/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/choxie-2.0.0.9/choxie/__init__.py
@@ -0,0 +1,1 @@
+# -*- coding: utf-8 -*-
diff --git a/distutils2/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py b/distutils2/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/choxie-2.0.0.9/choxie/chocolate.py
@@ -0,0 +1,10 @@
+# -*- 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
diff --git a/distutils2/tests/fake_dists/choxie-2.0.0.9/truffles.py b/distutils2/tests/fake_dists/choxie-2.0.0.9/truffles.py
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/choxie-2.0.0.9/truffles.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+from choxie.chocolate import Chocolate
+
+class Truffle(Chocolate):
+ """A truffle."""
diff --git a/distutils2/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO b/distutils2/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/coconuts-aster-10.3.egg-info/PKG-INFO
@@ -0,0 +1,5 @@
+Metadata-Version: 1.2
+Name: coconuts-aster
+Version: 10.3
+Provides-Dist: strawberry (0.6)
+Provides-Dist: banana (0.4)
diff --git a/distutils2/tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER b/distutils2/tests/fake_dists/grammar-1.0a4.dist-info/INSTALLER
new file mode 100644
diff --git a/distutils2/tests/fake_dists/grammar-1.0a4.dist-info/METADATA b/distutils2/tests/fake_dists/grammar-1.0a4.dist-info/METADATA
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/grammar-1.0a4.dist-info/METADATA
@@ -0,0 +1,5 @@
+Metadata-Version: 1.2
+Name: grammar
+Version: 1.0a4
+Requires-Dist: truffles (>=1.2)
+Author: Sherlock Holmes
diff --git a/distutils2/tests/fake_dists/grammar-1.0a4.dist-info/RECORD b/distutils2/tests/fake_dists/grammar-1.0a4.dist-info/RECORD
new file mode 100644
diff --git a/distutils2/tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED b/distutils2/tests/fake_dists/grammar-1.0a4.dist-info/REQUESTED
new file mode 100644
diff --git a/distutils2/tests/fake_dists/grammar-1.0a4/grammar/__init__.py b/distutils2/tests/fake_dists/grammar-1.0a4/grammar/__init__.py
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/grammar-1.0a4/grammar/__init__.py
@@ -0,0 +1,1 @@
+# -*- coding: utf-8 -*-
diff --git a/distutils2/tests/fake_dists/grammar-1.0a4/grammar/utils.py b/distutils2/tests/fake_dists/grammar-1.0a4/grammar/utils.py
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/grammar-1.0a4/grammar/utils.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+from random import randint
+
+def is_valid_grammar(sentence):
+ if randint(0, 10) < 2:
+ return False
+ else:
+ return True
diff --git a/distutils2/tests/fake_dists/nut-funkyversion.egg-info b/distutils2/tests/fake_dists/nut-funkyversion.egg-info
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/nut-funkyversion.egg-info
@@ -0,0 +1,3 @@
+Metadata-Version: 1.2
+Name: nut
+Version: funkyversion
diff --git a/distutils2/tests/fake_dists/strawberry-0.6.egg b/distutils2/tests/fake_dists/strawberry-0.6.egg
new file mode 100644
index 0000000000000000000000000000000000000000..6d160e8b161031ae52638514843592187925b757
GIT binary patch
literal 1402
zc$`yK)KALH(=X28%1l#;R!B%nEKbc!%uQ8LF-TCbRZuEUEh#N1$<NOz)-}*GOExsr
zEvPioGuBH@Pghci<toWY%~MFNNKIBKs4U6I&jU&+=q4*DW#$&-7nLX!R~G9i<QH3m
z7<vVXB^i2|dBv$kB^m}GVTed#QZb0uP**Wf*VMFDNGr<ERX|v)mz<xQo0ylPmzr2y
z84uK6l9-dD05v};Kfk27q$sffVnb?0W{Cz|OhZ#sNkK`)L5V9hr#O{MK_N9cBOmB5
zh0J1wy!;XcpxYIa^NWg7lS>r9UQWv|0ty0Ufu2)H%gjmDgJ}xL0otCbPy`8@OrXVy
z$=M1e`3iV~M*-+)g_5F5g~as4%sjABpm0h{1UV)xlPkcRnT3l11j?rGw_!j6Vhl12
zuI}!-o_=or`X%`V@j0nwsX2Nj6(yk|oD9qiubF*7xU_<sfsy3}GXn#dK$usBW}XPL
zdBOgnLC&thaML(|CUIalO$4ZygTbxvn9cJCa*K8Yd7pq-5ZSZ<ZwLcyl*$Bd9}q?}
z%GU3+-(d%yJ-@a8FngYAJv`50)he!AtAbSdVkX?4u~u<++)e34e{3~_X5O|jF|J;B
zC3xwx-m|X72inV6*}Y^gtiD*1wpsGu-O%40%MDdGSw1j37$$7q`bcR1wJ4|Sg?Z{f
zrybfGuv%=l^lLM>i9RnLxBqqeU~_GC)B*8Q%^m+PITr6Z&8t31F+o4>xK^{d-p7?^
zx~J#&ZkWuS9<SEFoOjj3--Yb2mtJ3TW&iZ@1KQ^&N4;mX5B=!V_JJD_rYtj1!&DME
z6jJkm@f=@}pP5%u3=ddwV4#ZQ4p;_;F99Leli98tF#_2jEDOYNBU4ffQu9($^O7s$
zb29U?!NvlU3?q{qGp<A<0cUS%tYT!qnS@v&NeIJT2(vL05VF~)kj+L(POOmRgw<qR
zi3wsdBiLjKCI)OtixrZz@HiMVb`TC;(kREwfG@6CA#sJpco7ENF@*?^LS_Q-7U0dw
Q22#ojgxi7o*D`~60Pqom)c^nh
diff --git a/distutils2/tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER b/distutils2/tests/fake_dists/towel_stuff-0.1.dist-info/INSTALLER
new file mode 100644
diff --git a/distutils2/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA b/distutils2/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/towel_stuff-0.1.dist-info/METADATA
@@ -0,0 +1,7 @@
+Metadata-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)
diff --git a/distutils2/tests/fake_dists/towel_stuff-0.1.dist-info/RECORD b/distutils2/tests/fake_dists/towel_stuff-0.1.dist-info/RECORD
new file mode 100644
diff --git a/distutils2/tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED b/distutils2/tests/fake_dists/towel_stuff-0.1.dist-info/REQUESTED
new file mode 100644
diff --git a/distutils2/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py b/distutils2/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/towel_stuff-0.1/towel_stuff/__init__.py
@@ -0,0 +1,18 @@
+# -*- 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
diff --git a/distutils2/tests/fake_dists/truffles-5.0.egg-info b/distutils2/tests/fake_dists/truffles-5.0.egg-info
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/fake_dists/truffles-5.0.egg-info
@@ -0,0 +1,3 @@
+Metadata-Version: 1.2
+Name: truffles
+Version: 5.0
diff --git a/distutils2/tests/fixer/fix_idioms.py b/distutils2/tests/fixer/fix_idioms.py
--- a/distutils2/tests/fixer/fix_idioms.py
+++ b/distutils2/tests/fixer/fix_idioms.py
@@ -101,18 +101,18 @@
def transform_isinstance(self, node, results):
x = results["x"].clone() # The thing inside of type()
T = results["T"].clone() # The type being compared against
- x.set_prefix("")
- T.set_prefix(" ")
+ x.prefix = ""
+ T.prefix = " "
test = Call(Name("isinstance"), [x, Comma(), T])
if "n" in results:
- test.set_prefix(" ")
+ test.prefix = " "
test = Node(syms.not_test, [Name("not"), test])
- test.set_prefix(node.get_prefix())
+ test.prefix = getattr(node, 'prefix', ' ')
return test
def transform_while(self, node, results):
one = results["while"]
- one.replace(Name("True", prefix=one.get_prefix()))
+ one.replace(Name("True", prefix=one.prefix))
def transform_sort(self, node, results):
sort_stmt = results["sort"]
@@ -121,14 +121,14 @@
simple_expr = results.get("expr")
if list_call:
- list_call.replace(Name("sorted", prefix=list_call.get_prefix()))
+ list_call.replace(Name("sorted", prefix=list_call.prefix))
elif simple_expr:
new = simple_expr.clone()
- new.set_prefix("")
+ new.prefix = ""
simple_expr.replace(Call(Name("sorted"), [new],
- prefix=simple_expr.get_prefix()))
+ prefix=simple_expr.prefix))
else:
raise RuntimeError("should not have reached here")
sort_stmt.remove()
if next_stmt:
- next_stmt[0].set_prefix(sort_stmt.get_prefix())
+ next_stmt[0].prefix = sort_stmt._prefix
diff --git a/distutils2/tests/pypi_server.py b/distutils2/tests/pypi_server.py
--- a/distutils2/tests/pypi_server.py
+++ b/distutils2/tests/pypi_server.py
@@ -22,48 +22,44 @@
Then, the server must have only one port to rely on, eg.
- >>> server.fulladress()
+ >>> server.fulladdress()
"http://ip:port/"
It could be simple to have one HTTP server, relaying the requests to the two
implementations (static HTTP and XMLRPC over HTTP).
"""
-import os.path
+import os
+import queue
import select
-import socket
import threading
-
-# several packages had different names in Python 2.x
-try:
- import queue
- import socketserver
- from http.server import HTTPServer, SimpleHTTPRequestHandler
- from xmlrpc.server import SimpleXMLRPCServer
-except ImportError:
- import Queue as queue
- import SocketServer as socketserver
- from BaseHTTPServer import HTTPServer
- from SimpleHTTPServer import SimpleHTTPRequestHandler
- from SimpleXMLRPCServer import SimpleXMLRPCServer
+import socketserver
+from functools import wraps
+from http.server import HTTPServer, SimpleHTTPRequestHandler
+from xmlrpc.server import SimpleXMLRPCServer
from distutils2.tests import unittest
-PYPI_DEFAULT_STATIC_PATH = os.path.dirname(os.path.abspath(__file__)) + "/pypiserver"
+PYPI_DEFAULT_STATIC_PATH = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), 'pypiserver')
+
def use_xmlrpc_server(*server_args, **server_kwargs):
server_kwargs['serve_xmlrpc'] = True
return use_pypi_server(*server_args, **server_kwargs)
+
def use_http_server(*server_args, **server_kwargs):
server_kwargs['serve_xmlrpc'] = False
return use_pypi_server(*server_args, **server_kwargs)
+
def use_pypi_server(*server_args, **server_kwargs):
"""Decorator to make use of the PyPIServer for test methods,
just when needed, and not for the entire duration of the testcase.
"""
def wrapper(func):
+ @wraps(func)
def wrapped(*args, **kwargs):
server = PyPIServer(*server_args, **server_kwargs)
server.start()
@@ -74,16 +70,15 @@
return wrapped
return wrapper
+
class PyPIServerTestCase(unittest.TestCase):
def setUp(self):
super(PyPIServerTestCase, self).setUp()
self.pypi = PyPIServer()
self.pypi.start()
+ self.addCleanup(self.pypi.stop)
- def tearDown(self):
- super(PyPIServerTestCase, self).tearDown()
- self.pypi.stop()
class PyPIServer(threading.Thread):
"""PyPI Mocked server.
@@ -93,8 +88,8 @@
"""
def __init__(self, test_static_path=None,
- static_filesystem_paths=["default"],
- static_uri_paths=["simple"], serve_xmlrpc=False) :
+ static_filesystem_paths=None,
+ static_uri_paths=["simple", "packages"], serve_xmlrpc=False):
"""Initialize the server.
Default behavior is to start the HTTP server. You can either start the
@@ -110,6 +105,8 @@
threading.Thread.__init__(self)
self._run = True
self._serve_xmlrpc = serve_xmlrpc
+ if static_filesystem_paths is None:
+ static_filesystem_paths = ["default"]
#TODO allow to serve XMLRPC and HTTP static files at the same time.
if not self._serve_xmlrpc:
@@ -118,15 +115,18 @@
self.request_queue = queue.Queue()
self._requests = []
- self.default_response_status = 200
+ self.default_response_status = 404
self.default_response_headers = [('Content-type', 'text/plain')]
- self.default_response_data = "hello"
+ self.default_response_data = "The page does not exists"
# initialize static paths / filesystems
self.static_uri_paths = static_uri_paths
+
+ # append the static paths defined locally
if test_static_path is not None:
static_filesystem_paths.append(test_static_path)
- self.static_filesystem_paths = [PYPI_DEFAULT_STATIC_PATH + "/" + path
+ self.static_filesystem_paths = [
+ PYPI_DEFAULT_STATIC_PATH + "/" + path
for path in static_filesystem_paths]
else:
# XMLRPC server
@@ -136,7 +136,7 @@
self.server.register_introspection_functions()
self.server.register_instance(self.xmlrpc)
- self.address = (self.server.server_name, self.server.server_port)
+ self.address = ('127.0.0.1', self.server.server_port)
# to not have unwanted outputs.
self.server.RequestHandlerClass.log_request = lambda *_: None
@@ -150,6 +150,9 @@
def stop(self):
"""self shutdown is not supported for python < 2.6"""
self._run = False
+ if self.is_alive():
+ self.join()
+ self.server.server_close()
def get_next_response(self):
return (self.default_response_status,
@@ -177,29 +180,23 @@
# we need to access the pypi server while serving the content
pypi_server = None
- def do_POST(self):
- return self.serve_request()
- def do_GET(self):
- return self.serve_request()
- def do_DELETE(self):
- return self.serve_request()
- def do_PUT(self):
- return self.serve_request()
-
def serve_request(self):
"""Serve the content.
Also record the requests to be accessed later. If trying to access an
url matching a static uri, serve static content, otherwise serve
what is provided by the `get_next_response` method.
+
+ If nothing is defined there, return a 404 header.
"""
# record the request. Read the input only on PUT or POST requests
if self.command in ("PUT", "POST"):
- if 'content-length' in self.headers.dict:
+ if 'content-length' in self.headers:
request_data = self.rfile.read(
int(self.headers['content-length']))
else:
request_data = self.rfile.read()
+
elif self.command in ("GET", "DELETE"):
request_data = ''
@@ -220,13 +217,19 @@
try:
if self.path.endswith("/"):
relative_path += "index.html"
- file = open(fs_path + relative_path)
- data = file.read()
+
if relative_path.endswith('.tar.gz'):
- headers=[('Content-type', 'application/x-gtar')]
+ with open(fs_path + relative_path, 'br') as file:
+ data = file.read()
+ headers = [('Content-type', 'application/x-gtar')]
else:
- headers=[('Content-type', 'text/html')]
+ with open(fs_path + relative_path) as file:
+ data = file.read().encode()
+ headers = [('Content-type', 'text/html')]
+
+ headers.append(('Content-Length', len(data)))
self.make_response(data, headers=headers)
+
except IOError:
pass
@@ -239,6 +242,8 @@
status, headers, data = self.pypi_server.get_next_response()
self.make_response(data, status, headers)
+ do_POST = do_GET = do_DELETE = do_PUT = serve_request
+
def make_response(self, data, status=200,
headers=[('Content-type', 'text/html')]):
"""Send the response to the HTTP client"""
@@ -254,18 +259,24 @@
for header, value in headers:
self.send_header(header, value)
self.end_headers()
+
+ if type(data) is str:
+ data = data.encode()
+
self.wfile.write(data)
+
class PyPIXMLRPCServer(SimpleXMLRPCServer):
def server_bind(self):
"""Override server_bind to store the server name."""
socketserver.TCPServer.server_bind(self)
host, port = self.socket.getsockname()[:2]
- self.server_name = socket.getfqdn(host)
self.server_port = port
+
class MockDist(object):
"""Fake distribution, used in the Mock PyPI Server"""
+
def __init__(self, name, version="1.0", hidden=False, url="http://url/",
type="sdist", filename="", size=10000,
digest="123456", downloads=7, has_sig=False,
@@ -377,6 +388,7 @@
'summary': self.summary,
}
+
class XMLRPCMockIndex(object):
"""Mock XMLRPC server"""
@@ -386,7 +398,7 @@
def add_distributions(self, dists):
for dist in dists:
- self._dists.append(MockDist(**dist))
+ self._dists.append(MockDist(**dist))
def set_distributions(self, dists):
self._dists = []
diff --git a/distutils2/tests/pypi_test_server.py b/distutils2/tests/pypi_test_server.py
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/pypi_test_server.py
@@ -0,0 +1,59 @@
+"""Test PyPI Server implementation at testpypi.python.org, to use in tests.
+
+This is a drop-in replacement for the mock pypi server for testing against a
+real pypi server hosted by python.org especially for testing against.
+"""
+
+import unittest
+
+PYPI_DEFAULT_STATIC_PATH = None
+
+
+def use_xmlrpc_server(*server_args, **server_kwargs):
+ server_kwargs['serve_xmlrpc'] = True
+ return use_pypi_server(*server_args, **server_kwargs)
+
+
+def use_http_server(*server_args, **server_kwargs):
+ server_kwargs['serve_xmlrpc'] = False
+ return use_pypi_server(*server_args, **server_kwargs)
+
+
+def use_pypi_server(*server_args, **server_kwargs):
+ """Decorator to make use of the PyPIServer for test methods,
+ just when needed, and not for the entire duration of the testcase.
+ """
+ def wrapper(func):
+ def wrapped(*args, **kwargs):
+ server = PyPIServer(*server_args, **server_kwargs)
+ func(server=server, *args, **kwargs)
+ return wrapped
+ return wrapper
+
+
+class PyPIServerTestCase(unittest.TestCase):
+
+ def setUp(self):
+ super(PyPIServerTestCase, self).setUp()
+ self.pypi = PyPIServer()
+ self.pypi.start()
+ self.addCleanup(self.pypi.stop)
+
+
+class PyPIServer(object):
+ """Shim to access testpypi.python.org, for testing a real server."""
+
+ def __init__(self, test_static_path=None,
+ static_filesystem_paths=["default"],
+ static_uri_paths=["simple"], serve_xmlrpc=False):
+ self.address = ('testpypi.python.org', '80')
+
+ def start(self):
+ pass
+
+ def stop(self):
+ pass
+
+ @property
+ def full_address(self):
+ return "http://%s:%s" % self.address
diff --git a/distutils2/tests/pypiserver/downloads_with_md5/packages/source/f/foobar/foobar-0.1.tar.gz b/distutils2/tests/pypiserver/downloads_with_md5/packages/source/f/foobar/foobar-0.1.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..333961eb18a6e7db80fefd41c339ab218d5180c4
GIT binary patch
literal 110
zc$|~(=3uy!>FUeC{PvtR-ysJc)&sVu<PP%fK3N&$;N1M<j{R(=POen}XGQA#H*w#;
z=4~0pQ=DD>?9yZ7`(A1Di)P(6s!I71JWZ;--fWND`LA)=lAmk-7Jbj=XMlnFEsQ#U
Kd|Vkc7#IK&xGYxy
diff --git a/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/foobar-0.1.tar.gz b/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/foobar-0.1.tar.gz
deleted file mode 100644
Binary file distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/foobar-0.1.tar.gz has changed
diff --git a/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/index.html b/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/index.html
--- a/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/index.html
+++ b/distutils2/tests/pypiserver/downloads_with_md5/simple/foobar/index.html
@@ -1,3 +1,3 @@
<html><body>
-<a href="foobar-0.1.tar.gz#md5=d41d8cd98f00b204e9800998ecf8427e" rel="download">foobar-0.1.tar.gz</a><br/>
+<a href="foobar-0.1.tar.gz#md5=fe18804c5b722ff024cabdf514924fc4" rel="download">foobar-0.1.tar.gz</a><br/>
</body></html>
diff --git a/distutils2/tests/support.py b/distutils2/tests/support.py
--- a/distutils2/tests/support.py
+++ b/distutils2/tests/support.py
@@ -1,8 +1,7 @@
"""Support code for distutils2 test cases.
-Four helper classes are provided: LoggingCatcher, TempdirManager,
-EnvironGuard and WarningsCatcher. They are written to be used as mixins,
-e.g. ::
+A few helper classes are provided: LoggingCatcher, TempdirManager and
+EnvironRestorer. They are written to be used as mixins::
from distutils2.tests import unittest
from distutils2.tests.support import LoggingCatcher
@@ -21,83 +20,112 @@
tests of another command that needs them, a create_distribution function
and a skip_unless_symlink decorator.
+Also provided is a DummyCommand class, useful to mock commands in the
+tests of another command that needs them, a create_distribution function
+and a skip_unless_symlink decorator.
+
Each class or function has a docstring to explain its purpose and usage.
"""
+import codecs
import os
import shutil
+import logging
+import logging.handlers
+import subprocess
+import sys
+import weakref
import tempfile
-import warnings
-from copy import deepcopy
-import logging
+try:
+ import _thread, threading
+except ImportError:
+ _thread = None
+ threading = None
+try:
+ import zlib
+except ImportError:
+ zlib = None
-from distutils2 import logger
from distutils2.dist import Distribution
from distutils2.tests import unittest
-__all__ = ['LoggingCatcher', 'WarningsCatcher', 'TempdirManager',
- 'EnvironGuard', 'DummyCommand', 'unittest', 'create_distribution',
- 'skip_unless_symlink']
+__all__ = ['LoggingCatcher', 'TempdirManager', 'EnvironRestorer',
+ 'DummyCommand', 'unittest', 'create_distribution',
+ 'skip_unless_symlink', 'requires_zlib']
+
+
+logger = logging.getLogger('distutils2')
+logger2to3 = logging.getLogger('RefactoringTool')
+
+
+class _TestHandler(logging.handlers.BufferingHandler):
+ # stolen and adapted from test.support
+
+ def __init__(self):
+ logging.handlers.BufferingHandler.__init__(self, 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 catch logging calls.
+ """TestCase-compatible mixin to receive logging calls.
- Every log message that goes through distutils2.log will get appended to
- self.logs instead of being printed. You can check that your code logs
- warnings and errors as documented by inspecting that list; helper methods
- get_logs and clear_logs are also provided.
+ 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; if you test code that
+ generates logging messages but don't use get_logs, you have to flush
+ manually before doing other checks on logging message, otherwise you
+ will get irrelevant results. See example in test_command_check.
"""
def setUp(self):
super(LoggingCatcher, self).setUp()
- # TODO read the new logging docs and/or the python-dev posts about
- # logging and tests to properly use a handler instead of
- # monkey-patching
- self.old_log = logger._log
- logger._log = self._log
- logger.setLevel(logging.INFO)
- self.logs = []
-
- def _log(self, *args, **kw):
- self.logs.append(args)
+ self.loghandler = handler = _TestHandler()
+ self._old_levels = logger.level, logger2to3.level
+ logger.addHandler(handler)
+ logger.setLevel(logging.DEBUG) # we want all messages
+ logger2to3.setLevel(logging.CRITICAL) # we don't want 2to3 messages
def tearDown(self):
- logger._log = self.old_log
+ 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_levels[0])
+ logger2to3.setLevel(self._old_levels[1])
super(LoggingCatcher, self).tearDown()
def get_logs(self, *levels):
- """Return a list of caught messages with level in `levels`.
+ """Return all log messages with level in *levels*.
- Example: self.get_logs(log.WARN, log.DEBUG) -> list
+ Without explicit levels given, returns all messages. *levels* defaults
+ to all levels. 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.
+
+ Example: self.get_logs(logging.WARN, logging.DEBUG).
"""
- def _format(msg, args):
- if len(args) == 0:
- return msg
- return msg % args
- return [_format(msg, args) for level, msg, args
- in self.logs if level in levels]
-
- def clear_logs(self):
- """Empty the internal list of caught messages."""
- del self.logs[:]
-
-
-class WarningsCatcher(object):
-
- def setUp(self):
- self._orig_showwarning = warnings.showwarning
- warnings.showwarning = self._record_showwarning
- self.warnings = []
-
- def _record_showwarning(self, message, category, filename, lineno,
- file=None, line=None):
- self.warnings.append({"message": message, "category": category,
- "filename": filename, "lineno": lineno,
- "file": file, "line": line})
-
- def tearDown(self):
- warnings.showwarning = self._orig_showwarning
+ if not levels:
+ messages = [log.getMessage() for log in self.loghandler.buffer]
+ else:
+ messages = [log.getMessage() for log in self.loghandler.buffer
+ if log.levelno in levels]
+ self.loghandler.flush()
+ return messages
class TempdirManager(object):
@@ -109,24 +137,33 @@
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:
+ handle.close()
+ unlink(name)
+
+ os.chdir(self._olddir)
+ shutil.rmtree(self._basetempdir)
super(TempdirManager, self).tearDown()
- shutil.rmtree(self._basetempdir, os.name in ('nt', 'cygwin'))
def mktempfile(self):
"""Create a read-write temporary file and return it."""
fd, fn = tempfile.mkstemp(dir=self._basetempdir)
os.close(fd)
- return open(fn, 'w+')
+ 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'):
+ 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,
@@ -134,11 +171,8 @@
"""
if isinstance(path, (list, tuple)):
path = os.path.join(*path)
- f = open(path, 'w')
- try:
+ with codecs.open(path, 'w', encoding=encoding) as f:
f.write(content)
- finally:
- f.close()
def create_dist(self, **kw):
"""Create a stub distribution object and files.
@@ -151,9 +185,6 @@
You can use self.write_file to write any file in that
directory, e.g. setup scripts or Python modules.
"""
- # Late import so that third parties can import support without
- # loading a ton of distutils2 modules in memory.
- from distutils2.dist import Distribution
if 'name' not in kw:
kw['name'] = 'foo'
tmp_dir = self.mkdtemp()
@@ -176,25 +207,34 @@
def assertIsNotFile(self, *args):
path = os.path.join(*args)
- assert not os.path.isfile(path), "%s exist" % path
+ self.assertFalse(os.path.isfile(path), "%r exists" % path)
-class EnvironGuard(object):
- """TestCase-compatible mixin to save and restore the environment."""
+
+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(EnvironGuard, self).setUp()
- self.old_environ = deepcopy(os.environ)
+ 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.old_environ.iteritems():
- if os.environ.get(key) != value:
- os.environ[key] = value
-
- for key in os.environ.keys():
- if key not in self.old_environ:
- del os.environ[key]
-
- super(EnvironGuard, self).tearDown()
+ 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 DummyCommand(object):
@@ -205,7 +245,7 @@
"""
def __init__(self, **kwargs):
- for kw, val in kwargs.iteritems():
+ for kw, val in kwargs.items():
setattr(self, kw, val)
def ensure_finalized(self):
@@ -234,8 +274,231 @@
return d
-try:
- from test.test_support import skip_unless_symlink
-except ImportError:
- skip_unless_symlink = unittest.skip(
- 'requires test.test_support.skip_unless_symlink')
+def fake_dec(*args, **kw):
+ """Fake decorator"""
+ def _wrap(func):
+ def __wrap(*args, **kw):
+ return func(*args, **kw)
+ return __wrap
+ return _wrap
+
+
+#try:
+# from test.support import skip_unless_symlink
+#except ImportError:
+# skip_unless_symlink = unittest.skip(
+# 'requires test.support.skip_unless_symlink')
+
+if os.name == 'java':
+ # Jython disallows @ in module names
+ TESTFN = '$test'
+else:
+ TESTFN = '@test'
+
+# Disambiguate TESTFN for parallel testing, while letting it remain a valid
+# module name.
+TESTFN = "{0}_{1}_tmp".format(TESTFN, os.getpid())
+
+
+# TESTFN_UNICODE is a non-ascii filename
+TESTFN_UNICODE = TESTFN + "-\xe0\xf2\u0258\u0141\u011f"
+if sys.platform == 'darwin':
+ # In Mac OS X's VFS API file names are, by definition, canonically
+ # decomposed Unicode, encoded using UTF-8. See QA1173:
+ # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html
+ import unicodedata
+ TESTFN_UNICODE = unicodedata.normalize('NFD', TESTFN_UNICODE)
+TESTFN_ENCODING = sys.getfilesystemencoding()
+
+# TESTFN_UNENCODABLE is a filename (str type) that should *not* be able to be
+# encoded by the filesystem encoding (in strict mode). It can be None if we
+# cannot generate such filename.
+TESTFN_UNENCODABLE = None
+if os.name in ('nt', 'ce'):
+ # skip win32s (0) or Windows 9x/ME (1)
+ if sys.getwindowsversion().platform >= 2:
+ # Different kinds of characters from various languages to minimize the
+ # probability that the whole name is encodable to MBCS (issue #9819)
+ TESTFN_UNENCODABLE = TESTFN + "-\u5171\u0141\u2661\u0363\uDC80"
+ try:
+ TESTFN_UNENCODABLE.encode(TESTFN_ENCODING)
+ except UnicodeEncodeError:
+ pass
+ else:
+ print('WARNING: The filename %r CAN be encoded by the filesystem encoding (%s). '
+ 'Unicode filename tests may not be effective'
+ % (TESTFN_UNENCODABLE, TESTFN_ENCODING))
+ TESTFN_UNENCODABLE = None
+# Mac OS X denies unencodable filenames (invalid utf-8)
+elif sys.platform != 'darwin':
+ try:
+ # ascii and utf-8 cannot encode the byte 0xff
+ b'\xff'.decode(TESTFN_ENCODING)
+ except UnicodeDecodeError:
+ # 0xff will be encoded using the surrogate character u+DCFF
+ try:
+ TESTFN_UNENCODABLE = TESTFN \
+ + b'-\xff'.decode(TESTFN_ENCODING, 'surrogateescape')
+ except LookupError:
+ pass
+ else:
+ # File system encoding (eg. ISO-8859-* encodings) can encode
+ # the byte 0xff. Skip some unicode filename tests.
+ pass
+
+def unlink(filename):
+ try:
+ os.unlink(filename)
+ except OSError:
+ error = sys.exc_info()[1]
+ # The filename need not exist.
+ if error.errno not in (errno.ENOENT, errno.ENOTDIR):
+ raise
+
+def _filter_suite(suite, pred):
+ """Recursively filter test cases in a suite based on a predicate."""
+ newtests = []
+ for test in suite._tests:
+ if isinstance(test, unittest.TestSuite):
+ _filter_suite(test, pred)
+ newtests.append(test)
+ else:
+ if pred(test):
+ newtests.append(test)
+ suite._tests = newtests
+
+class Error(Exception):
+ """Base class for regression test exceptions."""
+
+class TestFailed(Error):
+ """Test failed."""
+
+
+verbose = True
+failfast = False
+
+def _run_suite(suite):
+ """Run tests from a unittest.TestSuite-derived class."""
+ if verbose:
+ runner = unittest.TextTestRunner(sys.stdout, verbosity=2,
+ failfast=failfast)
+ else:
+ runner = BasicTestRunner()
+
+ result = runner.run(suite)
+ if not result.wasSuccessful():
+ if len(result.errors) == 1 and not result.failures:
+ err = result.errors[0][1]
+ elif len(result.failures) == 1 and not result.errors:
+ err = result.failures[0][1]
+ else:
+ err = "multiple errors occurred"
+ if not verbose: err += "; run in verbose mode for details"
+ raise TestFailed(err)
+
+match_tests = None
+
+def run_unittest(*classes):
+ """Run tests from unittest.TestCase-derived classes."""
+ valid_types = (unittest.TestSuite, unittest.TestCase)
+ suite = unittest.TestSuite()
+ for cls in classes:
+ if isinstance(cls, basestring):
+ if cls in sys.modules:
+ suite.addTest(unittest.findTestCases(sys.modules[cls]))
+ else:
+ raise ValueError("str arguments must be keys in sys.modules")
+ elif isinstance(cls, valid_types):
+ suite.addTest(cls)
+ else:
+ suite.addTest(unittest.makeSuite(cls))
+ def case_pred(test):
+ if match_tests is None:
+ return True
+ for name in test.id().split("."):
+ if fnmatch.fnmatchcase(name, match_tests):
+ return True
+ return False
+ _filter_suite(suite, case_pred)
+ _run_suite(suite)
+
+
+def reap_threads(func):
+ """Use this function when threads are being used. This will
+ ensure that the threads are cleaned up even when the test fails.
+ If threading is unavailable this function does nothing.
+ """
+ if not _thread:
+ return func
+
+ @functools.wraps(func)
+ def decorator(*args):
+ key = threading_setup()
+ try:
+ return func(*args)
+ finally:
+ threading_cleanup(*key)
+ return decorator
+
+def reap_children():
+ """Use this function at the end of test_main() whenever sub-processes
+ are started. This will help ensure that no extra children (zombies)
+ stick around to hog resources and create problems when looking
+ for refleaks.
+ """
+
+ # Reap all our dead child processes so we don't leave zombies around.
+ # These hog resources and might be causing some of the buildbots to die.
+ if hasattr(os, 'waitpid'):
+ any_process = -1
+ while True:
+ try:
+ # This will raise an exception on Windows. That's ok.
+ pid, status = os.waitpid(any_process, os.WNOHANG)
+ if pid == 0:
+ break
+ except:
+ break
+
+requires_zlib = unittest.skipUnless(zlib, 'requires zlib')
+
+# Executing the interpreter in a subprocess
+def _assert_python(expected_success, *args, **env_vars):
+ cmd_line = [sys.executable]
+ if not env_vars:
+ cmd_line.append('-E')
+ cmd_line.extend(args)
+ # Need to preserve the original environment, for in-place testing of
+ # shared library builds.
+ env = os.environ.copy()
+ env.update(env_vars)
+ p = subprocess.Popen(cmd_line, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ env=env)
+ try:
+ out, err = p.communicate()
+ finally:
+ subprocess._cleanup()
+ p.stdout.close()
+ p.stderr.close()
+ rc = p.returncode
+ err = strip_python_stderr(err)
+ if (rc and expected_success) or (not rc and not expected_success):
+ raise AssertionError(
+ "Process return code is %d, "
+ "stderr follows:\n%s" % (rc, err.decode('ascii', 'ignore')))
+ return rc, out, err
+
+def assert_python_ok(*args, **env_vars):
+ """
+ Assert that running the interpreter with `args` and optional environment
+ variables `env_vars` is ok and return a (return code, stdout, stderr) tuple.
+ """
+ return _assert_python(True, *args, **env_vars)
+
+def unload(name):
+ try:
+ del sys.modules[name]
+ except KeyError:
+ pass
+
diff --git a/distutils2/tests/test_ccompiler.py b/distutils2/tests/test_ccompiler.py
--- a/distutils2/tests/test_ccompiler.py
+++ b/distutils2/tests/test_ccompiler.py
@@ -1,10 +1,10 @@
-"""Tests for distutils.ccompiler."""
-from distutils2.compiler.ccompiler import CCompiler
+"""Tests for distutils.compiler.ccompiler."""
+
+from distutils2.compiler import ccompiler
from distutils2.tests import unittest, support
-class CCompilerTestCase(support.EnvironGuard, unittest.TestCase):
-
+class CCompilerTestCase(unittest.TestCase):
pass # XXX need some tests on CCompiler
diff --git a/distutils2/tests/test_command_bdist.py b/distutils2/tests/test_command_bdist.py
--- a/distutils2/tests/test_command_bdist.py
+++ b/distutils2/tests/test_command_bdist.py
@@ -1,13 +1,13 @@
"""Tests for distutils.command.bdist."""
from distutils2 import util
-from distutils2.tests import run_unittest
+from distutils2.command.bdist import bdist, show_formats
-from distutils2.command.bdist import bdist, show_formats
from distutils2.tests import unittest, support, captured_stdout
class BuildTestCase(support.TempdirManager,
+ support.LoggingCatcher,
unittest.TestCase):
def _mock_get_platform(self):
@@ -74,4 +74,4 @@
return unittest.makeSuite(BuildTestCase)
if __name__ == '__main__':
- run_unittest(test_suite())
+ unittest.main(defaultTest='test_suite')
diff --git a/distutils2/tests/test_command_bdist_dumb.py b/distutils2/tests/test_command_bdist_dumb.py
--- a/distutils2/tests/test_command_bdist_dumb.py
+++ b/distutils2/tests/test_command_bdist_dumb.py
@@ -1,54 +1,34 @@
"""Tests for distutils.command.bdist_dumb."""
-import sys
import os
-
-# zlib is not used here, but if it's not available
-# test_simple_built will fail
-try:
- import zlib
-except ImportError:
- zlib = None
-
-from distutils2.tests import run_unittest, unittest
+import distutils2.util
from distutils2.dist import Distribution
from distutils2.command.bdist_dumb import bdist_dumb
-from distutils2.tests import support
+from distutils2.tests import unittest, support
+from distutils2.tests.support import requires_zlib
-SETUP_PY = """\
-from distutils.run import setup
-import foo
-
-setup(name='foo', version='0.1', py_modules=['foo'],
- url='xxx', author='xxx', author_email='xxx')
-
-"""
class BuildDumbTestCase(support.TempdirManager,
support.LoggingCatcher,
- support.EnvironGuard,
unittest.TestCase):
def setUp(self):
super(BuildDumbTestCase, self).setUp()
self.old_location = os.getcwd()
- self.old_sys_argv = sys.argv, sys.argv[:]
def tearDown(self):
os.chdir(self.old_location)
- sys.argv = self.old_sys_argv[0]
- sys.argv[:] = self.old_sys_argv[1]
+ distutils2.util._path_created.clear()
super(BuildDumbTestCase, self).tearDown()
- @unittest.skipUnless(zlib, "requires zlib")
+ @requires_zlib
def test_simple_built(self):
# let's create a simple package
tmp_dir = self.mkdtemp()
pkg_dir = os.path.join(tmp_dir, 'foo')
os.mkdir(pkg_dir)
- self.write_file((pkg_dir, 'setup.py'), SETUP_PY)
self.write_file((pkg_dir, 'foo.py'), '#')
self.write_file((pkg_dir, 'MANIFEST.in'), 'include foo.py')
self.write_file((pkg_dir, 'README'), '')
@@ -57,10 +37,7 @@
'py_modules': ['foo'],
'url': 'xxx', 'author': 'xxx',
'author_email': 'xxx'})
- dist.script_name = 'setup.py'
os.chdir(pkg_dir)
-
- sys.argv = ['setup.py']
cmd = bdist_dumb(dist)
# so the output is the same no matter
@@ -97,8 +74,9 @@
default = cmd.default_format[os.name]
self.assertEqual(cmd.format, default)
+
def test_suite():
return unittest.makeSuite(BuildDumbTestCase)
if __name__ == '__main__':
- run_unittest(test_suite())
+ unittest.main(defaultTest='test_suite')
diff --git a/distutils2/tests/test_command_bdist_msi.py b/distutils2/tests/test_command_bdist_msi.py
--- a/distutils2/tests/test_command_bdist_msi.py
+++ b/distutils2/tests/test_command_bdist_msi.py
@@ -1,9 +1,8 @@
"""Tests for distutils.command.bdist_msi."""
import sys
-from distutils2.tests import run_unittest
+from distutils2.tests import unittest, support
-from distutils2.tests import unittest, support
class BDistMSITestCase(support.TempdirManager,
support.LoggingCatcher,
@@ -17,8 +16,10 @@
cmd = bdist_msi(dist)
cmd.ensure_finalized()
+
def test_suite():
return unittest.makeSuite(BDistMSITestCase)
+
if __name__ == '__main__':
- run_unittest(test_suite())
+ unittest.main(defaultTest='test_suite')
diff --git a/distutils2/tests/test_command_bdist_wininst.py b/distutils2/tests/test_command_bdist_wininst.py
--- a/distutils2/tests/test_command_bdist_wininst.py
+++ b/distutils2/tests/test_command_bdist_wininst.py
@@ -1,7 +1,8 @@
"""Tests for distutils.command.bdist_wininst."""
-from distutils2.tests import unittest, support, run_unittest
from distutils2.command.bdist_wininst import bdist_wininst
+from distutils2.tests import unittest, support
+
class BuildWinInstTestCase(support.TempdirManager,
support.LoggingCatcher,
@@ -20,10 +21,12 @@
# and make sure it finds it and returns its content
# no matter what platform we have
exe_file = cmd.get_exe_bytes()
- self.assertTrue(len(exe_file) > 10)
+ self.assertGreater(len(exe_file), 10)
+
def test_suite():
return unittest.makeSuite(BuildWinInstTestCase)
+
if __name__ == '__main__':
- run_unittest(test_suite())
+ unittest.main(defaultTest='test_suite')
diff --git a/distutils2/tests/test_command_build.py b/distutils2/tests/test_command_build.py
--- a/distutils2/tests/test_command_build.py
+++ b/distutils2/tests/test_command_build.py
@@ -3,8 +3,9 @@
import sys
from distutils2.command.build import build
+from sysconfig import get_platform
from distutils2.tests import unittest, support
-from distutils2._backport.sysconfig import get_platform
+
class BuildTestCase(support.TempdirManager,
support.LoggingCatcher,
@@ -40,12 +41,13 @@
self.assertEqual(cmd.build_temp, wanted)
# build_scripts is build/scripts-x.x
- wanted = os.path.join(cmd.build_base, 'scripts-' + sys.version[0:3])
+ wanted = os.path.join(cmd.build_base, 'scripts-' + sys.version[0:3])
self.assertEqual(cmd.build_scripts, wanted)
# executable is os.path.normpath(sys.executable)
self.assertEqual(cmd.executable, os.path.normpath(sys.executable))
+
def test_suite():
return unittest.makeSuite(BuildTestCase)
diff --git a/distutils2/tests/test_command_build_clib.py b/distutils2/tests/test_command_build_clib.py
--- a/distutils2/tests/test_command_build_clib.py
+++ b/distutils2/tests/test_command_build_clib.py
@@ -2,10 +2,11 @@
import os
import sys
+from distutils2.util import find_executable
from distutils2.command.build_clib import build_clib
-from distutils2.errors import DistutilsSetupError
+from distutils2.errors import PackagingSetupError
from distutils2.tests import unittest, support
-from distutils2.util import find_executable
+
class BuildCLibTestCase(support.TempdirManager,
support.LoggingCatcher,
@@ -16,24 +17,24 @@
cmd = build_clib(dist)
# 'libraries' option must be a list
- self.assertRaises(DistutilsSetupError, cmd.check_library_list, 'foo')
+ self.assertRaises(PackagingSetupError, cmd.check_library_list, 'foo')
# each element of 'libraries' must a 2-tuple
- self.assertRaises(DistutilsSetupError, cmd.check_library_list,
+ self.assertRaises(PackagingSetupError, cmd.check_library_list,
['foo1', 'foo2'])
# first element of each tuple in 'libraries'
# must be a string (the library name)
- self.assertRaises(DistutilsSetupError, cmd.check_library_list,
+ self.assertRaises(PackagingSetupError, cmd.check_library_list,
[(1, 'foo1'), ('name', 'foo2')])
# library name may not contain directory separators
- self.assertRaises(DistutilsSetupError, cmd.check_library_list,
+ self.assertRaises(PackagingSetupError, cmd.check_library_list,
[('name', 'foo1'),
('another/name', 'foo2')])
# second element of each tuple must be a dictionary (build info)
- self.assertRaises(DistutilsSetupError, cmd.check_library_list,
+ self.assertRaises(PackagingSetupError, cmd.check_library_list,
[('name', {}),
('another', 'foo2')])
@@ -48,10 +49,10 @@
# "in 'libraries' option 'sources' must be present and must be
# a list of source filenames
cmd.libraries = [('name', {})]
- self.assertRaises(DistutilsSetupError, cmd.get_source_files)
+ self.assertRaises(PackagingSetupError, cmd.get_source_files)
cmd.libraries = [('name', {'sources': 1})]
- self.assertRaises(DistutilsSetupError, cmd.get_source_files)
+ self.assertRaises(PackagingSetupError, cmd.get_source_files)
cmd.libraries = [('name', {'sources': ['a', 'b']})]
self.assertEqual(cmd.get_source_files(), ['a', 'b'])
@@ -64,24 +65,24 @@
self.assertEqual(cmd.get_source_files(), ['a', 'b', 'c', 'd'])
def test_build_libraries(self):
-
pkg_dir, dist = self.create_dist()
cmd = build_clib(dist)
- class FakeCompiler:
+
+ class FakeCompiler(object):
def compile(*args, **kw):
pass
create_static_lib = compile
cmd.compiler = FakeCompiler()
- # build_libraries is also doing a bit of typoe checking
+ # build_libraries is also doing a bit of type checking
lib = [('name', {'sources': 'notvalid'})]
- self.assertRaises(DistutilsSetupError, cmd.build_libraries, lib)
+ self.assertRaises(PackagingSetupError, cmd.build_libraries, lib)
- lib = [('name', {'sources': list()})]
+ lib = [('name', {'sources': []})]
cmd.build_libraries(lib)
- lib = [('name', {'sources': tuple()})]
+ lib = [('name', {'sources': ()})]
cmd.build_libraries(lib)
def test_finalize_options(self):
@@ -97,13 +98,10 @@
self.assertEqual(cmd.include_dirs, [])
cmd.distribution.libraries = 'WONTWORK'
- self.assertRaises(DistutilsSetupError, cmd.finalize_options)
+ self.assertRaises(PackagingSetupError, cmd.finalize_options)
+ @unittest.skipIf(sys.platform == 'win32', 'disabled on win32')
def test_run(self):
- # can't test on windows
- if sys.platform == 'win32':
- return
-
pkg_dir, dist = self.create_dist()
cmd = build_clib(dist)
@@ -127,13 +125,14 @@
if ccmd is None:
continue
if find_executable(ccmd[0]) is None:
- return # can't test
+ raise unittest.SkipTest("can't test")
# this should work
cmd.run()
# let's check the result
- self.assertTrue('libfoo.a' in os.listdir(build_temp))
+ self.assertIn('libfoo.a', os.listdir(build_temp))
+
def test_suite():
return unittest.makeSuite(BuildCLibTestCase)
diff --git a/distutils2/tests/test_command_build_ext.py b/distutils2/tests/test_command_build_ext.py
--- a/distutils2/tests/test_command_build_ext.py
+++ b/distutils2/tests/test_command_build_ext.py
@@ -1,26 +1,29 @@
+import os
import sys
-import os
+import site
import shutil
+import sysconfig
+import textwrap
from StringIO import StringIO
+from distutils2.dist import Distribution
+from distutils2.errors import (UnknownFileError, CompileError,
+ PackagingPlatformError)
+from distutils2.command.build_ext import build_ext
+from distutils2.compiler.extension import Extension
+from .support import assert_python_ok
-import distutils2.tests
-from distutils2.tests import unittest
-from distutils2.compiler.extension import Extension
-from distutils2.dist import Distribution
-from distutils2.command.build_ext import build_ext
-from distutils2.tests import support
-from distutils2.errors import (UnknownFileError, DistutilsSetupError,
- CompileError)
-from distutils2._backport import sysconfig
+from distutils2.tests import support, unittest, verbose, unload
-# http://bugs.python.org/issue4373
-# Don't load the xx module more than once.
-ALREADY_TESTED = False
-CURDIR = os.path.abspath(os.path.dirname(__file__))
-
def _get_source_filename():
- return os.path.join(CURDIR, 'xxmodule.c')
+ # use installed copy if available
+ tests_f = os.path.join(os.path.dirname(__file__), 'xxmodule.c')
+ if os.path.exists(tests_f):
+ return tests_f
+ # otherwise try using copy from build directory
+ srcdir = sysconfig.get_config_var('srcdir')
+ return os.path.join(srcdir, 'Modules', 'xxmodule.c')
+
class BuildExtTestCase(support.TempdirManager,
support.LoggingCatcher,
@@ -30,25 +33,51 @@
# Note that we're making changes to sys.path
super(BuildExtTestCase, self).setUp()
self.tmp_dir = self.mkdtemp()
- self.sys_path = sys.path, sys.path[:]
- sys.path.append(self.tmp_dir)
- shutil.copy(_get_source_filename(), self.tmp_dir)
+ filename = _get_source_filename()
+ if os.path.exists(filename):
+ shutil.copy(filename, self.tmp_dir)
+ self.old_user_base = site.USER_BASE
+ site.USER_BASE = self.mkdtemp()
+ build_ext.USER_BASE = site.USER_BASE
+
+ def tearDown(self):
+ # Get everything back to normal
if sys.version > "2.6":
- import site
- self.old_user_base = site.USER_BASE
- site.USER_BASE = self.mkdtemp()
- from distutils2.command import build_ext
- build_ext.USER_BASE = site.USER_BASE
+ site.USER_BASE = self.old_user_base
+ build_ext.USER_BASE = self.old_user_base
- # XXX only works with 2.6 > -- dunno why yet
- @unittest.skipIf(sys.version < '2.6', 'requires Python 2.6 or higher')
+ super(BuildExtTestCase, self).tearDown()
+
+ def _fixup_command(self, cmd):
+ # When Python was build with --enable-shared, -L. is not good enough
+ # to find the libpython<blah>.so. This is because regrtest runs it
+ # under a tempdir, not in the top level where the .so lives. By the
+ # time we've gotten here, Python's already been chdir'd to the
+ # tempdir.
+ #
+ # To further add to the fun, we can't just add library_dirs to the
+ # Extension() instance because that doesn't get plumbed through to the
+ # final compiler command.
+ if (sysconfig.get_config_var('Py_ENABLE_SHARED') and
+ not sys.platform.startswith('win')):
+ runshared = sysconfig.get_config_var('RUNSHARED')
+ if runshared is None:
+ cmd.library_dirs = ['.']
+ else:
+ name, equals, value = runshared.partition('=')
+ cmd.library_dirs = value.split(os.pathsep)
+
def test_build_ext(self):
- global ALREADY_TESTED
xx_c = os.path.join(self.tmp_dir, 'xxmodule.c')
+ if not os.path.exists(xx_c):
+ # skipping if we cannot find it
+ return
xx_ext = Extension('xx', [xx_c])
dist = Distribution({'name': 'xx', 'ext_modules': [xx_ext]})
dist.package_dir = self.tmp_dir
cmd = build_ext(dist)
+ self._fixup_command(cmd)
+
if os.name == "nt":
# On Windows, we must build a debug version iff running
# a debug build of Python
@@ -57,7 +86,7 @@
cmd.build_temp = self.tmp_dir
old_stdout = sys.stdout
- if not distutils2.tests.verbose:
+ if not verbose:
# silence compiler output
sys.stdout = StringIO()
try:
@@ -66,44 +95,32 @@
finally:
sys.stdout = old_stdout
- if ALREADY_TESTED:
- return
- else:
- ALREADY_TESTED = True
+ code = """if 1:
+ import sys
+ sys.path.insert(0, %r)
- import xx
+ import xx
- for attr in ('error', 'foo', 'new', 'roj'):
- self.assertTrue(hasattr(xx, attr))
+ for attr in ('error', 'foo', 'new', 'roj'):
+ assert hasattr(xx, attr)
- self.assertEqual(xx.foo(2, 5), 7)
- self.assertEqual(xx.foo(13,15), 28)
- self.assertEqual(xx.new().demo(), None)
- doc = 'This is a template module just for instruction.'
- self.assertEqual(xx.__doc__, doc)
- self.assertTrue(isinstance(xx.Null(), xx.Null))
- self.assertTrue(isinstance(xx.Str(), xx.Str))
-
- def tearDown(self):
- # Get everything back to normal
- distutils2.tests.unload('xx')
- sys.path = self.sys_path[0]
- sys.path[:] = self.sys_path[1]
- if sys.version > "2.6":
- import site
- site.USER_BASE = self.old_user_base
- from distutils2.command import build_ext
- build_ext.USER_BASE = self.old_user_base
-
- super(BuildExtTestCase, self).tearDown()
+ assert xx.foo(2, 5) == 7
+ assert xx.foo(13, 15) == 28
+ assert xx.new().demo() is None
+ doc = 'This is a template module just for instruction.'
+ assert xx.__doc__ == doc
+ assert isinstance(xx.Null(), xx.Null)
+ assert isinstance(xx.Str(), xx.Str)"""
+ code = code % self.tmp_dir
+ assert_python_ok('-c', code)
def test_solaris_enable_shared(self):
dist = Distribution({'name': 'xx'})
cmd = build_ext(dist)
old = sys.platform
- sys.platform = 'sunos