diff --git a/.coveralls.yml b/.coveralls.yml
index 91a8273c..77b56740 100644
--- a/.coveralls.yml
+++ b/.coveralls.yml
@@ -1,2 +1 @@
-service_name: travis-pro
-repo_token: 1OI4obkPMbvZy6i3ADYYPgxFulDJGOIU7
+repo_token: 1OI4obkPMbvZy6i3ADYYPgxFulDJGOIU7
\ No newline at end of file
diff --git a/.project b/.project
deleted file mode 100644
index 8f8a283e..00000000
--- a/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- DragonPy
-
-
-
-
-
- org.python.pydev.PyDevBuilder
-
-
-
-
-
- org.python.pydev.pythonNature
-
-
diff --git a/.pydevproject b/.pydevproject
deleted file mode 100644
index f7587e92..00000000
--- a/.pydevproject
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-Default
-python 3.0
-
-/DragonPy
-
-
diff --git a/.travis.yml b/.travis.yml
index 9a8087b0..ffeacd9f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,18 +1,28 @@
+# Config file for automatic testing at travis-ci.org
+
language: python
+
python:
- - "2.7"
- - "3.4"
- - "pypy"
+ - "2.7"
+ - "3.4"
+ - "pypy"
+
install:
- - pip install dragonlib
-# - pip install coveralls
+ - "pip install ."
+ - "pip install coveralls"
+ - ./download_ROMs.sh
+
script:
- # "python setup.py test" will not run the tests from dragonlib
- - python -m unittest discover
-# coverage run --source=dragonpy setup.py test
-#after_success:
-# coveralls
+ # - python -m unittest discover
+ - coverage run --source=dragonpy ./setup.py test
+
+after_success:
+ coveralls
+
branches:
- only:
- - master
- - stable
+ only:
+ - master
+ - stable
+
+notifications:
+ irc: "irc.freenode.org#pylucid"
diff --git a/DragonPy_CLI.py b/DragonPy_CLI.py
index 676759e0..5eeea6f1 100755
--- a/DragonPy_CLI.py
+++ b/DragonPy_CLI.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python
# coding: utf-8
"""
@@ -6,15 +6,26 @@
~~~~~~~~~~~~~~
:created: 2013 by Jens Diemer - www.jensdiemer.de
- :copyleft: 2013-2014 by the DragonPy team, see AUTHORS for more details.
+ :copyleft: 2013-2015 by the DragonPy team, see AUTHORS for more details.
:license: GNU GPL v3 or above, see LICENSE for more details.
"""
from __future__ import absolute_import, division, print_function
-
-from dragonpy.DragonPy_CLI import get_cli
+import sys
if __name__ == "__main__":
- cli = get_cli()
- cli.run()
+ if not hasattr(sys, 'real_prefix'):
+ print()
+ print("="*79)
+ print("ERROR: virtual environment is not activated ?!?")
+ print("\ne.g.:")
+ print("\t~ $ cd ~/DragonPy_env/")
+ print("\t~/DragonPy_env $ source bin/activate")
+ print("\t~/DragonPy_env $ cd src/dragonpy")
+ print("\t~/DragonPy_env/src/dragonpy $ %s" % " ".join(sys.argv))
+ print("-"*79)
+ print()
+
+ from dragonpy.core.cli import cli
+ cli()
diff --git a/README.creole b/README.creole
index fb142989..3ce9f28c 100644
--- a/README.creole
+++ b/README.creole
@@ -2,6 +2,13 @@
DragonPy is a Open source (GPL v3 or later) emulator for the 30 years old homecomputer {{{Dragon 32}}} and {{{Tandy TRS-80 Color Computer}}} (CoCo)...
+The [[https://github.com/6809/MC6809|MC6809]] project is used to emulate the 6809 CPU.
+
+| {{https://travis-ci.org/jedie/DragonPy.svg|Build Status on travis-ci.org}} | [[https://travis-ci.org/jedie/DragonPy/|travis-ci.org/jedie/DragonPy]] |
+| {{https://coveralls.io/repos/jedie/DragonPy/badge.svg|Coverage Status on coveralls.io}} | [[https://coveralls.io/r/jedie/DragonPy|coveralls.io/r/jedie/DragonPy]] |
+| {{https://requires.io/github/jedie/DragonPy/requirements.svg?branch=django-cms|Requirements Status on requires.io}} | [[https://requires.io/github/jedie/DragonPy/requirements/|requires.io/github/jedie/DragonPy/requirements/]] |
+
+
Dragon 32 and 64 ROMs in Text mode:
{{http://www.jensdiemer.de/static/jensdiemer.de/screenshots/20140805_DragonPy_Dragon64_01.png|screenshot Dragon 64}}
@@ -43,7 +50,7 @@ The Hardware are //only// the 6809 CPU, a 6522 Versatile Interface Adapter and t
Current state is completely not usable. The 6522 is only a dummy implementation.
It makes only sense to display some trace lines, e.g.:
{{{
-...path/to/DragonPy$ python2 DragonPy_CLI.py --verbosity 5 --machine=Vectrex run --trace --max_ops 1
+(DragonPy_env)~/DragonPy_env$ bin/python src/dragonpy/DragonPy_CLI.py --verbosity 5 --machine=Vectrex run --trace --max_ops 1
}}}
@@ -60,7 +67,7 @@ The "renumbering" tool can be found in the editor window under "tools"
You can also run the BASIC Editor without the Emulator:
{{{
-...path/to/DragonPy$ python2 DragonPy_CLI.py editor
+(DragonPy_env)~/DragonPy_env$ bin/python src/dragonpy/DragonPy_CLI.py editor
}}}
A rudimentary BASIC source code highlighting is available and looks like this:
@@ -69,27 +76,56 @@ A rudimentary BASIC source code highlighting is available and looks like this:
Special feature: The Line number that are used in GOTO, SOGUB etc. are extra marked on the left side.
-Currently the code are only differate into:
-* DATA
-* Strings
-* Comments
-* Code
-TODO: parse the code, to coloring it differently.
+== installation
-=== ROMs
+=== Linux
-The Dragon ROMs can be downloaded here:
-* http://archive.worldofdragon.org/archive/index.php?dir=Roms/Dragon/
+The is a virtualenv bootstrap file, created with [[https://github.com/jedie/bootstrap_env|bootstrap_env]], for easy installation.
-There is a simple shell script to automatic download and extract the Dragon ROMs:
+Get the bootstrap file:
+{{{
+/home/FooBar$ wget https://raw.githubusercontent.com/jedie/DragonPy/master/boot_dragonpy.py
+}}}
+
+
+There are tree types of installation:
+|=option |= desciption
+| **pypi** | use [[http://www.python.org/pypi/|Python Package Index]] (for all normal user!)
+| **git_readonly** | use {{{git}}} to get the sourcecode (for developer without write access)
+| **dev** | use {{{git}}} with write access
+
+e.g.:
{{{
-...path/to/DragonPy$ ./download_ROMs.sh
+/home/FooBar$ python3 boot_dragonpy.py ~/DragonPy_env --install_type git_readonly
}}}
+This creates a virtualenv in **{{{~/DragonPy_env}}}** and used {{{git}}} to checkout the needed repositories.
+
+In this case (using --install_type=**git_readonly**) the git repository are in: **.../DragonPy_env/src/**
+So you can easy update them e.g.:
+{{{
+/home/FooBar$ cd ~/DragonPy_env/src/dragonpy
+/home/FooBar/DragonPy_env/src/dragonpy$ git pull
+}}}
+
+=== Windows
-=== Quick start:
+There are several ways to install the project under windows.
-**TODO:** External {{{dragonlib}}} dependency is needed: https://github.com/6809/dragonlib
+The following is hopeful the easiest one:
+
+* Install Python 3, e.g.: https://www.python.org/downloads/
+* Download the {{{DWLOAD Server}}} git snapshot from Github: [[https://github.com/jedie/DragonPy/archive/master.zip|master.zip]]
+* Extract the Archive somewhere
+* Maybe, adjust paths in {{{boot_dwload_server.cmd}}}
+* Run {{{boot_dwload_server.cmd}}}
+
+The default {{{boot_dwload_server.cmd}}} will install via {{{Python Package Index}}} (PyPi) into {{{%APPDATA%\DragonPy_env}}}
+
+There exist a batch file for easy startup the server under Windows:
+* [[https://github.com/jedie/DragonPy/blob/master/scripts/start_dwload_server.cmd|/scripts/start_dwload_server.cmd]]
+
+Copy it into {{{%APPDATA%\DragonPy_env\start_dwload_server.cmd}}} and edit it for your needs.
{{{
# clone the repro:
@@ -106,51 +142,79 @@ There is a simple shell script to automatic download and extract the Dragon ROMs
~/DragonPy$ python2 DragonPy_CLI.py --machine=Dragon32 run
}}}
-== example
+
+== ROMs
+
+The Dragon ROMs can be downloaded here:
+* http://archive.worldofdragon.org/archive/index.php?dir=Roms/Dragon/
+
+There is a simple shell script to automatic download and extract the Dragon ROMs:
+{{{
+/home/FooBar/DragonPy_env$ cd src/dragonpy/
+/home/FooBar/DragonPy_env/src/dragonpy$ ./download_ROMs.sh
+}}}
+To verify existing ROMs, just call the script again:
+{{{
+/home/FooBar/DragonPy_env$ cd src/dragonpy/
+/home/FooBar/DragonPy_env/src/dragonpy$ ./download_ROMs.sh
++ cd dragonpy/Dragon32/
++ source download_Dragon32_rom.sh
+++ '[' '!' -f d32.rom ']'
+++ set -x
+++ sha1sum -c d32.rom.sha1
+d32.rom: OK
+========================================================================
+...
+d64_ic17.rom: OK
+d64_ic18.rom: OK
+bas13.rom: OK
+extbas11.rom: OK
+EXT_BASIC_NO_USING.bin: OK
+ExBasROM.bin: OK
+}}}
+
+
+== cli example
start Dragon 32:
{{{
-...path/to/DragonPy$ python2 DragonPy_CLI.py --machine=Dragon32 run
+/home/FooBar$ cd DragonPy_env
+/home/FooBar/DragonPy_env$ source bin/activate
+(DragonPy_env)~/DragonPy_env$ DragonPy --machine=Dragon32 run
}}}
start Dragon 64:
{{{
-...path/to/DragonPy$ python2 DragonPy_CLI.py --machine=Dragon64 run
+(DragonPy_env)~/DragonPy_env$ DragonPy --machine=Dragon64 run
}}}
start CoCo with Extended Color Basic v1.1:
{{{
-...path/to/DragonPy$ python2 DragonPy_CLI.py --machine=CoCo2b run
+(DragonPy_env)~/DragonPy_env$ DragonPy --machine=CoCo2b run
}}}
start Multicomp 6809:
{{{
-...path/to/DragonPy$ python2 DragonPy_CLI.py --machine=Multicomp6809 run
+(DragonPy_env)~/DragonPy_env$ DragonPy --machine=Multicomp6809 run
}}}
start Lennart's 6809 single board computer:
{{{
-...path/to/DragonPy$ python2 DragonPy_CLI.py --machine=sbc09 run
+(DragonPy_env)~/DragonPy_env$ DragonPy --machine=sbc09 run
}}}
== unittests ==
-[[https://travis-ci.org/jedie/DragonPy|Travis CI Status]]: {{https://secure.travis-ci.org/jedie/DragonPy.svg?branch=master|DragonPy build status on travis-ci.org}}
-* https://travis-ci.org/jedie/DragonPy
-
=== run unittests ===
You can run tests with PyPy, Python 2 and Python 3:
{{{
-...path/to/DragonPy$ python -m unittest discover
+(DragonPy_env)~/DragonPy_env/src/dragonpy$ python -m unittest discover
}}}
-
-All variants:
+or:
{{{
-...path/to/DragonPy$ python2 -m unittest discover
-...path/to/DragonPy$ python3 -m unittest discover
-...path/to/DragonPy$ pypy -m unittest discover
+(DragonPy_env)~/DragonPy_env/src/dragonpy$ ./setup.py test
}}}
@@ -162,10 +226,10 @@ install [[https://pypi.python.org/pypi/coverage|coverage]] for python 2:
}}}
{{{
-...path/to/DragonPy$ coverage2 run --source=dragonpy setup.py test
-...path/to/DragonPy$ coverage2 coverage2 html
+...path/to/env/src/dragonpy$ coverage2 run --source=dragonpy setup.py test
+...path/to/env/src/dragonpy$ coverage2 coverage2 html
# e.g.:
-...path/to/DragonPy$ firefox htmlcov/index.html
+...path/to/env/src/dragonpy$ firefox htmlcov/index.html
}}}
@@ -257,27 +321,10 @@ e.g. The Dragon 32 6809 machine with a 14.31818 MHz crystal runs with:
0,895MHz (14,31818Mhz/16=0,895MHz) in other words: 895.000 CPU-cycles/sec.
-There is a simple benchmark. Run e.g.:
-{{{
-...path/to/DragonPy$ python2 DragonPy_CLI.py benchmark
-...path/to/DragonPy$ python3 DragonPy_CLI.py benchmark
-...path/to/DragonPy$ pypy DragonPy_CLI.py benchmark
-...path/to/DragonPy$ pypy3 DragonPy_CLI.py benchmark --loops 10
-}}}
-
-
== TODO:
-# Use bottle for http control server part
# implement more Dragon 32 periphery
-unimplemented OPs:
-
- * RESET
- * SWI / SWI2 / SWI3
- * SYNC
-
-
missing 6809 unittests after coverage run:
* MUL
@@ -395,6 +442,7 @@ Six is a Python 2 and 3 compatibility library.
== History
+* 26.05.2015 - v0.4.0 - The MC6809 code is out sourced to: https://github.com/6809/MC6809
* 15.12.2014 - v0.3.2 - Use [[http://pygments.org/|Pygments]] syntax highlighter in BASIC editor
* 08.10.2014 - Release as v0.3.1
* 30.09.2014 - Enhance the BASIC editor
diff --git a/boot_dragonpy.py b/boot_dragonpy.py
new file mode 100755
index 00000000..4526e337
--- /dev/null
+++ b/boot_dragonpy.py
@@ -0,0 +1,20399 @@
+#!/usr/bin/env python
+
+"""
+ WARNING: This file is generated with: bootstrap_env v0.4.5
+ https://pypi.python.org/pypi/bootstrap_env/
+ script file: 'create_bootstrap.py'
+ used '.../lib/python3.4/site-packages/virtualenv.py' v12.1.1
+ Python v3.4.0 (default, Apr 11 2014, 13:05:11) [GCC 4.8.2]
+"""
+
+__version__ = "12.1.1"
+virtualenv_version = __version__ # legacy
+
+import base64
+import sys
+import os
+import codecs
+import optparse
+import re
+import shutil
+import logging
+import tempfile
+import zlib
+import errno
+import glob
+import distutils.sysconfig
+from distutils.util import strtobool
+import struct
+import subprocess
+import tarfile
+
+if sys.version_info < (2, 6):
+ print('ERROR: %s' % sys.exc_info()[1])
+ print('ERROR: this script requires Python 2.6 or greater.')
+ sys.exit(101)
+
+try:
+ basestring
+except NameError:
+ basestring = str
+
+try:
+ import ConfigParser
+except ImportError:
+ import configparser as ConfigParser
+
+join = os.path.join
+py_version = 'python%s.%s' % (sys.version_info[0], sys.version_info[1])
+
+is_jython = sys.platform.startswith('java')
+is_pypy = hasattr(sys, 'pypy_version_info')
+is_win = (sys.platform == 'win32')
+is_cygwin = (sys.platform == 'cygwin')
+is_darwin = (sys.platform == 'darwin')
+abiflags = getattr(sys, 'abiflags', '')
+
+user_dir = os.path.expanduser('~')
+if is_win:
+ default_storage_dir = os.path.join(user_dir, 'virtualenv')
+else:
+ default_storage_dir = os.path.join(user_dir, '.virtualenv')
+default_config_file = os.path.join(default_storage_dir, 'virtualenv.ini')
+
+if is_pypy:
+ expected_exe = 'pypy'
+elif is_jython:
+ expected_exe = 'jython'
+else:
+ expected_exe = 'python'
+
+# Return a mapping of version -> Python executable
+# Only provided for Windows, where the information in the registry is used
+if not is_win:
+ def get_installed_pythons():
+ return {}
+else:
+ try:
+ import winreg
+ except ImportError:
+ import _winreg as winreg
+
+ def get_installed_pythons():
+ try:
+ python_core = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE,
+ "Software\\Python\\PythonCore")
+ except WindowsError:
+ # No registered Python installations
+ return {}
+ i = 0
+ versions = []
+ while True:
+ try:
+ versions.append(winreg.EnumKey(python_core, i))
+ i = i + 1
+ except WindowsError:
+ break
+ exes = dict()
+ for ver in versions:
+ try:
+ path = winreg.QueryValue(python_core, "%s\\InstallPath" % ver)
+ except WindowsError:
+ continue
+ exes[ver] = join(path, "python.exe")
+
+ winreg.CloseKey(python_core)
+
+ # Add the major versions
+ # Sort the keys, then repeatedly update the major version entry
+ # Last executable (i.e., highest version) wins with this approach
+ for ver in sorted(exes):
+ exes[ver[0]] = exes[ver]
+
+ return exes
+
+REQUIRED_MODULES = ['os', 'posix', 'posixpath', 'nt', 'ntpath', 'genericpath',
+ 'fnmatch', 'locale', 'encodings', 'codecs',
+ 'stat', 'UserDict', 'readline', 'copy_reg', 'types',
+ 're', 'sre', 'sre_parse', 'sre_constants', 'sre_compile',
+ 'zlib']
+
+REQUIRED_FILES = ['lib-dynload', 'config']
+
+majver, minver = sys.version_info[:2]
+if majver == 2:
+ if minver >= 6:
+ REQUIRED_MODULES.extend(['warnings', 'linecache', '_abcoll', 'abc'])
+ if minver >= 7:
+ REQUIRED_MODULES.extend(['_weakrefset'])
+elif majver == 3:
+ # Some extra modules are needed for Python 3, but different ones
+ # for different versions.
+ REQUIRED_MODULES.extend(['_abcoll', 'warnings', 'linecache', 'abc', 'io',
+ '_weakrefset', 'copyreg', 'tempfile', 'random',
+ '__future__', 'collections', 'keyword', 'tarfile',
+ 'shutil', 'struct', 'copy', 'tokenize', 'token',
+ 'functools', 'heapq', 'bisect', 'weakref',
+ 'reprlib'])
+ if minver >= 2:
+ REQUIRED_FILES[-1] = 'config-%s' % majver
+ if minver >= 3:
+ import sysconfig
+ platdir = sysconfig.get_config_var('PLATDIR')
+ REQUIRED_FILES.append(platdir)
+ # The whole list of 3.3 modules is reproduced below - the current
+ # uncommented ones are required for 3.3 as of now, but more may be
+ # added as 3.3 development continues.
+ REQUIRED_MODULES.extend([
+ #"aifc",
+ #"antigravity",
+ #"argparse",
+ #"ast",
+ #"asynchat",
+ #"asyncore",
+ "base64",
+ #"bdb",
+ #"binhex",
+ #"bisect",
+ #"calendar",
+ #"cgi",
+ #"cgitb",
+ #"chunk",
+ #"cmd",
+ #"codeop",
+ #"code",
+ #"colorsys",
+ #"_compat_pickle",
+ #"compileall",
+ #"concurrent",
+ #"configparser",
+ #"contextlib",
+ #"cProfile",
+ #"crypt",
+ #"csv",
+ #"ctypes",
+ #"curses",
+ #"datetime",
+ #"dbm",
+ #"decimal",
+ #"difflib",
+ #"dis",
+ #"doctest",
+ #"dummy_threading",
+ "_dummy_thread",
+ #"email",
+ #"filecmp",
+ #"fileinput",
+ #"formatter",
+ #"fractions",
+ #"ftplib",
+ #"functools",
+ #"getopt",
+ #"getpass",
+ #"gettext",
+ #"glob",
+ #"gzip",
+ "hashlib",
+ #"heapq",
+ "hmac",
+ #"html",
+ #"http",
+ #"idlelib",
+ #"imaplib",
+ #"imghdr",
+ "imp",
+ "importlib",
+ #"inspect",
+ #"json",
+ #"lib2to3",
+ #"logging",
+ #"macpath",
+ #"macurl2path",
+ #"mailbox",
+ #"mailcap",
+ #"_markupbase",
+ #"mimetypes",
+ #"modulefinder",
+ #"multiprocessing",
+ #"netrc",
+ #"nntplib",
+ #"nturl2path",
+ #"numbers",
+ #"opcode",
+ #"optparse",
+ #"os2emxpath",
+ #"pdb",
+ #"pickle",
+ #"pickletools",
+ #"pipes",
+ #"pkgutil",
+ #"platform",
+ #"plat-linux2",
+ #"plistlib",
+ #"poplib",
+ #"pprint",
+ #"profile",
+ #"pstats",
+ #"pty",
+ #"pyclbr",
+ #"py_compile",
+ #"pydoc_data",
+ #"pydoc",
+ #"_pyio",
+ #"queue",
+ #"quopri",
+ #"reprlib",
+ "rlcompleter",
+ #"runpy",
+ #"sched",
+ #"shelve",
+ #"shlex",
+ #"smtpd",
+ #"smtplib",
+ #"sndhdr",
+ #"socket",
+ #"socketserver",
+ #"sqlite3",
+ #"ssl",
+ #"stringprep",
+ #"string",
+ #"_strptime",
+ #"subprocess",
+ #"sunau",
+ #"symbol",
+ #"symtable",
+ #"sysconfig",
+ #"tabnanny",
+ #"telnetlib",
+ #"test",
+ #"textwrap",
+ #"this",
+ #"_threading_local",
+ #"threading",
+ #"timeit",
+ #"tkinter",
+ #"tokenize",
+ #"token",
+ #"traceback",
+ #"trace",
+ #"tty",
+ #"turtledemo",
+ #"turtle",
+ #"unittest",
+ #"urllib",
+ #"uuid",
+ #"uu",
+ #"wave",
+ #"weakref",
+ #"webbrowser",
+ #"wsgiref",
+ #"xdrlib",
+ #"xml",
+ #"xmlrpc",
+ #"zipfile",
+ ])
+ if minver >= 4:
+ REQUIRED_MODULES.extend([
+ 'operator',
+ '_collections_abc',
+ '_bootlocale',
+ ])
+
+if is_pypy:
+ # these are needed to correctly display the exceptions that may happen
+ # during the bootstrap
+ REQUIRED_MODULES.extend(['traceback', 'linecache'])
+
+class Logger(object):
+
+ """
+ Logging object for use in command-line script. Allows ranges of
+ levels, to avoid some redundancy of displayed information.
+ """
+
+ DEBUG = logging.DEBUG
+ INFO = logging.INFO
+ NOTIFY = (logging.INFO+logging.WARN)/2
+ WARN = WARNING = logging.WARN
+ ERROR = logging.ERROR
+ FATAL = logging.FATAL
+
+ LEVELS = [DEBUG, INFO, NOTIFY, WARN, ERROR, FATAL]
+
+ def __init__(self, consumers):
+ self.consumers = consumers
+ self.indent = 0
+ self.in_progress = None
+ self.in_progress_hanging = False
+
+ def debug(self, msg, *args, **kw):
+ self.log(self.DEBUG, msg, *args, **kw)
+ def info(self, msg, *args, **kw):
+ self.log(self.INFO, msg, *args, **kw)
+ def notify(self, msg, *args, **kw):
+ self.log(self.NOTIFY, msg, *args, **kw)
+ def warn(self, msg, *args, **kw):
+ self.log(self.WARN, msg, *args, **kw)
+ def error(self, msg, *args, **kw):
+ self.log(self.ERROR, msg, *args, **kw)
+ def fatal(self, msg, *args, **kw):
+ self.log(self.FATAL, msg, *args, **kw)
+ def log(self, level, msg, *args, **kw):
+ if args:
+ if kw:
+ raise TypeError(
+ "You may give positional or keyword arguments, not both")
+ args = args or kw
+ rendered = None
+ for consumer_level, consumer in self.consumers:
+ if self.level_matches(level, consumer_level):
+ if (self.in_progress_hanging
+ and consumer in (sys.stdout, sys.stderr)):
+ self.in_progress_hanging = False
+ sys.stdout.write('\n')
+ sys.stdout.flush()
+ if rendered is None:
+ if args:
+ rendered = msg % args
+ else:
+ rendered = msg
+ rendered = ' '*self.indent + rendered
+ if hasattr(consumer, 'write'):
+ consumer.write(rendered+'\n')
+ else:
+ consumer(rendered)
+
+ def start_progress(self, msg):
+ assert not self.in_progress, (
+ "Tried to start_progress(%r) while in_progress %r"
+ % (msg, self.in_progress))
+ if self.level_matches(self.NOTIFY, self._stdout_level()):
+ sys.stdout.write(msg)
+ sys.stdout.flush()
+ self.in_progress_hanging = True
+ else:
+ self.in_progress_hanging = False
+ self.in_progress = msg
+
+ def end_progress(self, msg='done.'):
+ assert self.in_progress, (
+ "Tried to end_progress without start_progress")
+ if self.stdout_level_matches(self.NOTIFY):
+ if not self.in_progress_hanging:
+ # Some message has been printed out since start_progress
+ sys.stdout.write('...' + self.in_progress + msg + '\n')
+ sys.stdout.flush()
+ else:
+ sys.stdout.write(msg + '\n')
+ sys.stdout.flush()
+ self.in_progress = None
+ self.in_progress_hanging = False
+
+ def show_progress(self):
+ """If we are in a progress scope, and no log messages have been
+ shown, write out another '.'"""
+ if self.in_progress_hanging:
+ sys.stdout.write('.')
+ sys.stdout.flush()
+
+ def stdout_level_matches(self, level):
+ """Returns true if a message at this level will go to stdout"""
+ return self.level_matches(level, self._stdout_level())
+
+ def _stdout_level(self):
+ """Returns the level that stdout runs at"""
+ for level, consumer in self.consumers:
+ if consumer is sys.stdout:
+ return level
+ return self.FATAL
+
+ def level_matches(self, level, consumer_level):
+ """
+ >>> l = Logger([])
+ >>> l.level_matches(3, 4)
+ False
+ >>> l.level_matches(3, 2)
+ True
+ >>> l.level_matches(slice(None, 3), 3)
+ False
+ >>> l.level_matches(slice(None, 3), 2)
+ True
+ >>> l.level_matches(slice(1, 3), 1)
+ True
+ >>> l.level_matches(slice(2, 3), 1)
+ False
+ """
+ if isinstance(level, slice):
+ start, stop = level.start, level.stop
+ if start is not None and start > consumer_level:
+ return False
+ if stop is not None and stop <= consumer_level:
+ return False
+ return True
+ else:
+ return level >= consumer_level
+
+ #@classmethod
+ def level_for_integer(cls, level):
+ levels = cls.LEVELS
+ if level < 0:
+ return levels[0]
+ if level >= len(levels):
+ return levels[-1]
+ return levels[level]
+
+ level_for_integer = classmethod(level_for_integer)
+
+# create a silent logger just to prevent this from being undefined
+# will be overridden with requested verbosity main() is called.
+logger = Logger([(Logger.LEVELS[-1], sys.stdout)])
+
+def mkdir(path):
+ if not os.path.exists(path):
+ logger.info('Creating %s', path)
+ os.makedirs(path)
+ else:
+ logger.info('Directory %s already exists', path)
+
+def copyfileordir(src, dest, symlink=True):
+ if os.path.isdir(src):
+ shutil.copytree(src, dest, symlink)
+ else:
+ shutil.copy2(src, dest)
+
+def copyfile(src, dest, symlink=True):
+ if not os.path.exists(src):
+ # Some bad symlink in the src
+ logger.warn('Cannot find file %s (bad symlink)', src)
+ return
+ if os.path.exists(dest):
+ logger.debug('File %s already exists', dest)
+ return
+ if not os.path.exists(os.path.dirname(dest)):
+ logger.info('Creating parent directories for %s', os.path.dirname(dest))
+ os.makedirs(os.path.dirname(dest))
+ if not os.path.islink(src):
+ srcpath = os.path.abspath(src)
+ else:
+ srcpath = os.readlink(src)
+ if symlink and hasattr(os, 'symlink') and not is_win:
+ logger.info('Symlinking %s', dest)
+ try:
+ os.symlink(srcpath, dest)
+ except (OSError, NotImplementedError):
+ logger.info('Symlinking failed, copying to %s', dest)
+ copyfileordir(src, dest, symlink)
+ else:
+ logger.info('Copying to %s', dest)
+ copyfileordir(src, dest, symlink)
+
+def writefile(dest, content, overwrite=True):
+ if not os.path.exists(dest):
+ logger.info('Writing %s', dest)
+ f = open(dest, 'wb')
+ f.write(content.encode('utf-8'))
+ f.close()
+ return
+ else:
+ f = open(dest, 'rb')
+ c = f.read()
+ f.close()
+ if c != content.encode("utf-8"):
+ if not overwrite:
+ logger.notify('File %s exists with different content; not overwriting', dest)
+ return
+ logger.notify('Overwriting %s with new content', dest)
+ f = open(dest, 'wb')
+ f.write(content.encode('utf-8'))
+ f.close()
+ else:
+ logger.info('Content %s already in place', dest)
+
+def rmtree(dir):
+ if os.path.exists(dir):
+ logger.notify('Deleting tree %s', dir)
+ shutil.rmtree(dir)
+ else:
+ logger.info('Do not need to delete %s; already gone', dir)
+
+def make_exe(fn):
+ if hasattr(os, 'chmod'):
+ oldmode = os.stat(fn).st_mode & 0xFFF # 0o7777
+ newmode = (oldmode | 0x16D) & 0xFFF # 0o555, 0o7777
+ os.chmod(fn, newmode)
+ logger.info('Changed mode of %s to %s', fn, oct(newmode))
+
+def _find_file(filename, dirs):
+ for dir in reversed(dirs):
+ files = glob.glob(os.path.join(dir, filename))
+ if files and os.path.isfile(files[0]):
+ return True, files[0]
+ return False, filename
+
+def file_search_dirs():
+ here = os.path.dirname(os.path.abspath(__file__))
+ dirs = ['.', here,
+ join(here, 'virtualenv_support')]
+ if os.path.splitext(os.path.dirname(__file__))[0] != 'virtualenv':
+ # Probably some boot script; just in case virtualenv is installed...
+ try:
+ import virtualenv
+ except ImportError:
+ pass
+ else:
+ dirs.append(os.path.join(os.path.dirname(virtualenv.__file__), 'virtualenv_support'))
+ return [d for d in dirs if os.path.isdir(d)]
+
+
+class UpdatingDefaultsHelpFormatter(optparse.IndentedHelpFormatter):
+ """
+ Custom help formatter for use in ConfigOptionParser that updates
+ the defaults before expanding them, allowing them to show up correctly
+ in the help listing
+ """
+ def expand_default(self, option):
+ if self.parser is not None:
+ self.parser.update_defaults(self.parser.defaults)
+ return optparse.IndentedHelpFormatter.expand_default(self, option)
+
+
+class ConfigOptionParser(optparse.OptionParser):
+ """
+ Custom option parser which updates its defaults by checking the
+ configuration files and environmental variables
+ """
+ def __init__(self, *args, **kwargs):
+ self.config = ConfigParser.RawConfigParser()
+ self.files = self.get_config_files()
+ self.config.read(self.files)
+ optparse.OptionParser.__init__(self, *args, **kwargs)
+
+ def get_config_files(self):
+ config_file = os.environ.get('VIRTUALENV_CONFIG_FILE', False)
+ if config_file and os.path.exists(config_file):
+ return [config_file]
+ return [default_config_file]
+
+ def update_defaults(self, defaults):
+ """
+ Updates the given defaults with values from the config files and
+ the environ. Does a little special handling for certain types of
+ options (lists).
+ """
+ # Then go and look for the other sources of configuration:
+ config = {}
+ # 1. config files
+ config.update(dict(self.get_config_section('virtualenv')))
+ # 2. environmental variables
+ config.update(dict(self.get_environ_vars()))
+ # Then set the options with those values
+ for key, val in config.items():
+ key = key.replace('_', '-')
+ if not key.startswith('--'):
+ key = '--%s' % key # only prefer long opts
+ option = self.get_option(key)
+ if option is not None:
+ # ignore empty values
+ if not val:
+ continue
+ # handle multiline configs
+ if option.action == 'append':
+ val = val.split()
+ else:
+ option.nargs = 1
+ if option.action == 'store_false':
+ val = not strtobool(val)
+ elif option.action in ('store_true', 'count'):
+ val = strtobool(val)
+ try:
+ val = option.convert_value(key, val)
+ except optparse.OptionValueError:
+ e = sys.exc_info()[1]
+ print("An error occurred during configuration: %s" % e)
+ sys.exit(3)
+ defaults[option.dest] = val
+ return defaults
+
+ def get_config_section(self, name):
+ """
+ Get a section of a configuration
+ """
+ if self.config.has_section(name):
+ return self.config.items(name)
+ return []
+
+ def get_environ_vars(self, prefix='VIRTUALENV_'):
+ """
+ Returns a generator with all environmental vars with prefix VIRTUALENV
+ """
+ for key, val in os.environ.items():
+ if key.startswith(prefix):
+ yield (key.replace(prefix, '').lower(), val)
+
+ def get_default_values(self):
+ """
+ Overridding to make updating the defaults after instantiation of
+ the option parser possible, update_defaults() does the dirty work.
+ """
+ if not self.process_default_values:
+ # Old, pre-Optik 1.5 behaviour.
+ return optparse.Values(self.defaults)
+
+ defaults = self.update_defaults(self.defaults.copy()) # ours
+ for option in self._get_all_options():
+ default = defaults.get(option.dest)
+ if isinstance(default, basestring):
+ opt_str = option.get_opt_string()
+ defaults[option.dest] = option.check_value(opt_str, default)
+ return optparse.Values(defaults)
+
+
+def main():
+ parser = ConfigOptionParser(
+ version=virtualenv_version,
+ usage="%prog [OPTIONS] DEST_DIR",
+ formatter=UpdatingDefaultsHelpFormatter())
+
+ parser.add_option(
+ '-v', '--verbose',
+ action='count',
+ dest='verbose',
+ default=0,
+ help="Increase verbosity.")
+
+ parser.add_option(
+ '-q', '--quiet',
+ action='count',
+ dest='quiet',
+ default=0,
+ help='Decrease verbosity.')
+
+ parser.add_option(
+ '-p', '--python',
+ dest='python',
+ metavar='PYTHON_EXE',
+ help='The Python interpreter to use, e.g., --python=python2.5 will use the python2.5 '
+ 'interpreter to create the new environment. The default is the interpreter that '
+ 'virtualenv was installed with (%s)' % sys.executable)
+
+ parser.add_option(
+ '--clear',
+ dest='clear',
+ action='store_true',
+ help="Clear out the non-root install and start from scratch.")
+
+ parser.set_defaults(system_site_packages=False)
+ parser.add_option(
+ '--no-site-packages',
+ dest='system_site_packages',
+ action='store_false',
+ help="DEPRECATED. Retained only for backward compatibility. "
+ "Not having access to global site-packages is now the default behavior.")
+
+ parser.add_option(
+ '--system-site-packages',
+ dest='system_site_packages',
+ action='store_true',
+ help="Give the virtual environment access to the global site-packages.")
+
+ parser.add_option(
+ '--always-copy',
+ dest='symlink',
+ action='store_false',
+ default=True,
+ help="Always copy files rather than symlinking.")
+
+ parser.add_option(
+ '--unzip-setuptools',
+ dest='unzip_setuptools',
+ action='store_true',
+ help="Unzip Setuptools when installing it.")
+
+ parser.add_option(
+ '--relocatable',
+ dest='relocatable',
+ action='store_true',
+ help='Make an EXISTING virtualenv environment relocatable. '
+ 'This fixes up scripts and makes all .pth files relative.')
+
+ parser.add_option(
+ '--no-setuptools',
+ dest='no_setuptools',
+ action='store_true',
+ help='Do not install setuptools (or pip) in the new virtualenv.')
+
+ parser.add_option(
+ '--no-pip',
+ dest='no_pip',
+ action='store_true',
+ help='Do not install pip in the new virtualenv.')
+
+ default_search_dirs = file_search_dirs()
+ parser.add_option(
+ '--extra-search-dir',
+ dest="search_dirs",
+ action="append",
+ metavar='DIR',
+ default=default_search_dirs,
+ help="Directory to look for setuptools/pip distributions in. "
+ "This option can be used multiple times.")
+
+ parser.add_option(
+ '--never-download',
+ dest="never_download",
+ action="store_true",
+ default=True,
+ help="DEPRECATED. Retained only for backward compatibility. This option has no effect. "
+ "Virtualenv never downloads pip or setuptools.")
+
+ parser.add_option(
+ '--prompt',
+ dest='prompt',
+ help='Provides an alternative prompt prefix for this environment.')
+
+ parser.add_option(
+ '--setuptools',
+ dest='setuptools',
+ action='store_true',
+ help="DEPRECATED. Retained only for backward compatibility. This option has no effect.")
+
+ parser.add_option(
+ '--distribute',
+ dest='distribute',
+ action='store_true',
+ help="DEPRECATED. Retained only for backward compatibility. This option has no effect.")
+
+ if 'extend_parser' in globals():
+ extend_parser(parser)
+
+ options, args = parser.parse_args()
+
+ global logger
+
+ if 'adjust_options' in globals():
+ adjust_options(options, args)
+
+ verbosity = options.verbose - options.quiet
+ logger = Logger([(Logger.level_for_integer(2 - verbosity), sys.stdout)])
+
+ if options.python and not os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'):
+ env = os.environ.copy()
+ interpreter = resolve_interpreter(options.python)
+ if interpreter == sys.executable:
+ logger.warn('Already using interpreter %s' % interpreter)
+ else:
+ logger.notify('Running virtualenv with interpreter %s' % interpreter)
+ env['VIRTUALENV_INTERPRETER_RUNNING'] = 'true'
+ file = __file__
+ if file.endswith('.pyc'):
+ file = file[:-1]
+ popen = subprocess.Popen([interpreter, file] + sys.argv[1:], env=env)
+ raise SystemExit(popen.wait())
+
+ if not args:
+ print('You must provide a DEST_DIR')
+ parser.print_help()
+ sys.exit(2)
+ if len(args) > 1:
+ print('There must be only one argument: DEST_DIR (you gave %s)' % (
+ ' '.join(args)))
+ parser.print_help()
+ sys.exit(2)
+
+ home_dir = args[0]
+
+ if os.environ.get('WORKING_ENV'):
+ logger.fatal('ERROR: you cannot run virtualenv while in a workingenv')
+ logger.fatal('Please deactivate your workingenv, then re-run this script')
+ sys.exit(3)
+
+ if 'PYTHONHOME' in os.environ:
+ logger.warn('PYTHONHOME is set. You *must* activate the virtualenv before using it')
+ del os.environ['PYTHONHOME']
+
+ if options.relocatable:
+ make_environment_relocatable(home_dir)
+ return
+
+ if not options.never_download:
+ logger.warn('The --never-download option is for backward compatibility only.')
+ logger.warn('Setting it to false is no longer supported, and will be ignored.')
+
+ create_environment(home_dir,
+ site_packages=options.system_site_packages,
+ clear=options.clear,
+ unzip_setuptools=options.unzip_setuptools,
+ prompt=options.prompt,
+ search_dirs=options.search_dirs,
+ never_download=True,
+ no_setuptools=options.no_setuptools,
+ no_pip=options.no_pip,
+ symlink=options.symlink)
+ if 'after_install' in globals():
+ after_install(options, home_dir)
+
+def call_subprocess(cmd, show_stdout=True,
+ filter_stdout=None, cwd=None,
+ raise_on_returncode=True, extra_env=None,
+ remove_from_env=None):
+ cmd_parts = []
+ for part in cmd:
+ if len(part) > 45:
+ part = part[:20]+"..."+part[-20:]
+ if ' ' in part or '\n' in part or '"' in part or "'" in part:
+ part = '"%s"' % part.replace('"', '\\"')
+ if hasattr(part, 'decode'):
+ try:
+ part = part.decode(sys.getdefaultencoding())
+ except UnicodeDecodeError:
+ part = part.decode(sys.getfilesystemencoding())
+ cmd_parts.append(part)
+ cmd_desc = ' '.join(cmd_parts)
+ if show_stdout:
+ stdout = None
+ else:
+ stdout = subprocess.PIPE
+ logger.debug("Running command %s" % cmd_desc)
+ if extra_env or remove_from_env:
+ env = os.environ.copy()
+ if extra_env:
+ env.update(extra_env)
+ if remove_from_env:
+ for varname in remove_from_env:
+ env.pop(varname, None)
+ else:
+ env = None
+ try:
+ proc = subprocess.Popen(
+ cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout,
+ cwd=cwd, env=env)
+ except Exception:
+ e = sys.exc_info()[1]
+ logger.fatal(
+ "Error %s while executing command %s" % (e, cmd_desc))
+ raise
+ all_output = []
+ if stdout is not None:
+ stdout = proc.stdout
+ encoding = sys.getdefaultencoding()
+ fs_encoding = sys.getfilesystemencoding()
+ while 1:
+ line = stdout.readline()
+ try:
+ line = line.decode(encoding)
+ except UnicodeDecodeError:
+ line = line.decode(fs_encoding)
+ if not line:
+ break
+ line = line.rstrip()
+ all_output.append(line)
+ if filter_stdout:
+ level = filter_stdout(line)
+ if isinstance(level, tuple):
+ level, line = level
+ logger.log(level, line)
+ if not logger.stdout_level_matches(level):
+ logger.show_progress()
+ else:
+ logger.info(line)
+ else:
+ proc.communicate()
+ proc.wait()
+ if proc.returncode:
+ if raise_on_returncode:
+ if all_output:
+ logger.notify('Complete output from command %s:' % cmd_desc)
+ logger.notify('\n'.join(all_output) + '\n----------------------------------------')
+ raise OSError(
+ "Command %s failed with error code %s"
+ % (cmd_desc, proc.returncode))
+ else:
+ logger.warn(
+ "Command %s had error code %s"
+ % (cmd_desc, proc.returncode))
+
+def filter_install_output(line):
+ if line.strip().startswith('running'):
+ return Logger.INFO
+ return Logger.DEBUG
+
+def find_wheels(projects, search_dirs):
+ """Find wheels from which we can import PROJECTS.
+
+ Scan through SEARCH_DIRS for a wheel for each PROJECT in turn. Return
+ a list of the first wheel found for each PROJECT
+ """
+
+ wheels = []
+
+ # Look through SEARCH_DIRS for the first suitable wheel. Don't bother
+ # about version checking here, as this is simply to get something we can
+ # then use to install the correct version.
+ for project in projects:
+ for dirname in search_dirs:
+ # This relies on only having "universal" wheels available.
+ # The pattern could be tightened to require -py2.py3-none-any.whl.
+ files = glob.glob(os.path.join(dirname, project + '-*.whl'))
+ if files:
+ wheels.append(os.path.abspath(files[0]))
+ break
+ else:
+ # We're out of luck, so quit with a suitable error
+ logger.fatal('Cannot find a wheel for %s' % (project,))
+
+ return wheels
+
+def install_wheel(project_names, py_executable, search_dirs=None):
+ if search_dirs is None:
+ search_dirs = file_search_dirs()
+
+ wheels = find_wheels(['setuptools', 'pip'], search_dirs)
+ pythonpath = os.pathsep.join(wheels)
+ findlinks = ' '.join(search_dirs)
+
+ cmd = [
+ py_executable, '-c',
+ 'import sys, pip; sys.exit(pip.main(["install", "--ignore-installed"] + sys.argv[1:]))',
+ ] + project_names
+ logger.start_progress('Installing %s...' % (', '.join(project_names)))
+ logger.indent += 2
+ try:
+ call_subprocess(cmd, show_stdout=False,
+ extra_env = {
+ 'PYTHONPATH': pythonpath,
+ 'PIP_FIND_LINKS': findlinks,
+ 'PIP_USE_WHEEL': '1',
+ 'PIP_PRE': '1',
+ 'PIP_NO_INDEX': '1'
+ }
+ )
+ finally:
+ logger.indent -= 2
+ logger.end_progress()
+
+def create_environment(home_dir, site_packages=False, clear=False,
+ unzip_setuptools=False,
+ prompt=None, search_dirs=None, never_download=False,
+ no_setuptools=False, no_pip=False, symlink=True):
+ """
+ Creates a new environment in ``home_dir``.
+
+ If ``site_packages`` is true, then the global ``site-packages/``
+ directory will be on the path.
+
+ If ``clear`` is true (default False) then the environment will
+ first be cleared.
+ """
+ home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir)
+
+ py_executable = os.path.abspath(install_python(
+ home_dir, lib_dir, inc_dir, bin_dir,
+ site_packages=site_packages, clear=clear, symlink=symlink))
+
+ install_distutils(home_dir)
+
+ if not no_setuptools:
+ to_install = ['setuptools']
+ if not no_pip:
+ to_install.append('pip')
+ install_wheel(to_install, py_executable, search_dirs)
+
+ install_activate(home_dir, bin_dir, prompt)
+
+def is_executable_file(fpath):
+ return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
+
+def path_locations(home_dir):
+ """Return the path locations for the environment (where libraries are,
+ where scripts go, etc)"""
+ # XXX: We'd use distutils.sysconfig.get_python_inc/lib but its
+ # prefix arg is broken: http://bugs.python.org/issue3386
+ if is_win:
+ # Windows has lots of problems with executables with spaces in
+ # the name; this function will remove them (using the ~1
+ # format):
+ mkdir(home_dir)
+ if ' ' in home_dir:
+ import ctypes
+ GetShortPathName = ctypes.windll.kernel32.GetShortPathNameW
+ size = max(len(home_dir)+1, 256)
+ buf = ctypes.create_unicode_buffer(size)
+ try:
+ u = unicode
+ except NameError:
+ u = str
+ ret = GetShortPathName(u(home_dir), buf, size)
+ if not ret:
+ print('Error: the path "%s" has a space in it' % home_dir)
+ print('We could not determine the short pathname for it.')
+ print('Exiting.')
+ sys.exit(3)
+ home_dir = str(buf.value)
+ lib_dir = join(home_dir, 'Lib')
+ inc_dir = join(home_dir, 'Include')
+ bin_dir = join(home_dir, 'Scripts')
+ if is_jython:
+ lib_dir = join(home_dir, 'Lib')
+ inc_dir = join(home_dir, 'Include')
+ bin_dir = join(home_dir, 'bin')
+ elif is_pypy:
+ lib_dir = home_dir
+ inc_dir = join(home_dir, 'include')
+ bin_dir = join(home_dir, 'bin')
+ elif not is_win:
+ lib_dir = join(home_dir, 'lib', py_version)
+ multiarch_exec = '/usr/bin/multiarch-platform'
+ if is_executable_file(multiarch_exec):
+ # In Mageia (2) and Mandriva distros the include dir must be like:
+ # virtualenv/include/multiarch-x86_64-linux/python2.7
+ # instead of being virtualenv/include/python2.7
+ p = subprocess.Popen(multiarch_exec, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ # stdout.strip is needed to remove newline character
+ inc_dir = join(home_dir, 'include', stdout.strip(), py_version + abiflags)
+ else:
+ inc_dir = join(home_dir, 'include', py_version + abiflags)
+ bin_dir = join(home_dir, 'bin')
+ return home_dir, lib_dir, inc_dir, bin_dir
+
+
+def change_prefix(filename, dst_prefix):
+ prefixes = [sys.prefix]
+
+ if is_darwin:
+ prefixes.extend((
+ os.path.join("/Library/Python", sys.version[:3], "site-packages"),
+ os.path.join(sys.prefix, "Extras", "lib", "python"),
+ os.path.join("~", "Library", "Python", sys.version[:3], "site-packages"),
+ # Python 2.6 no-frameworks
+ os.path.join("~", ".local", "lib","python", sys.version[:3], "site-packages"),
+ # System Python 2.7 on OSX Mountain Lion
+ os.path.join("~", "Library", "Python", sys.version[:3], "lib", "python", "site-packages")))
+
+ if hasattr(sys, 'real_prefix'):
+ prefixes.append(sys.real_prefix)
+ if hasattr(sys, 'base_prefix'):
+ prefixes.append(sys.base_prefix)
+ prefixes = list(map(os.path.expanduser, prefixes))
+ prefixes = list(map(os.path.abspath, prefixes))
+ # Check longer prefixes first so we don't split in the middle of a filename
+ prefixes = sorted(prefixes, key=len, reverse=True)
+ filename = os.path.abspath(filename)
+ for src_prefix in prefixes:
+ if filename.startswith(src_prefix):
+ _, relpath = filename.split(src_prefix, 1)
+ if src_prefix != os.sep: # sys.prefix == "/"
+ assert relpath[0] == os.sep
+ relpath = relpath[1:]
+ return join(dst_prefix, relpath)
+ assert False, "Filename %s does not start with any of these prefixes: %s" % \
+ (filename, prefixes)
+
+def copy_required_modules(dst_prefix, symlink):
+ import imp
+ # If we are running under -p, we need to remove the current
+ # directory from sys.path temporarily here, so that we
+ # definitely get the modules from the site directory of
+ # the interpreter we are running under, not the one
+ # virtualenv.py is installed under (which might lead to py2/py3
+ # incompatibility issues)
+ _prev_sys_path = sys.path
+ if os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'):
+ sys.path = sys.path[1:]
+ try:
+ for modname in REQUIRED_MODULES:
+ if modname in sys.builtin_module_names:
+ logger.info("Ignoring built-in bootstrap module: %s" % modname)
+ continue
+ try:
+ f, filename, _ = imp.find_module(modname)
+ except ImportError:
+ logger.info("Cannot import bootstrap module: %s" % modname)
+ else:
+ if f is not None:
+ f.close()
+ # special-case custom readline.so on OS X, but not for pypy:
+ if modname == 'readline' and sys.platform == 'darwin' and not (
+ is_pypy or filename.endswith(join('lib-dynload', 'readline.so'))):
+ dst_filename = join(dst_prefix, 'lib', 'python%s' % sys.version[:3], 'readline.so')
+ elif modname == 'readline' and sys.platform == 'win32':
+ # special-case for Windows, where readline is not a
+ # standard module, though it may have been installed in
+ # site-packages by a third-party package
+ pass
+ else:
+ dst_filename = change_prefix(filename, dst_prefix)
+ copyfile(filename, dst_filename, symlink)
+ if filename.endswith('.pyc'):
+ pyfile = filename[:-1]
+ if os.path.exists(pyfile):
+ copyfile(pyfile, dst_filename[:-1], symlink)
+ finally:
+ sys.path = _prev_sys_path
+
+
+def subst_path(prefix_path, prefix, home_dir):
+ prefix_path = os.path.normpath(prefix_path)
+ prefix = os.path.normpath(prefix)
+ home_dir = os.path.normpath(home_dir)
+ if not prefix_path.startswith(prefix):
+ logger.warn('Path not in prefix %r %r', prefix_path, prefix)
+ return
+ return prefix_path.replace(prefix, home_dir, 1)
+
+
+def install_python(home_dir, lib_dir, inc_dir, bin_dir, site_packages, clear, symlink=True):
+ """Install just the base environment, no distutils patches etc"""
+ if sys.executable.startswith(bin_dir):
+ print('Please use the *system* python to run this script')
+ return
+
+ if clear:
+ rmtree(lib_dir)
+ ## FIXME: why not delete it?
+ ## Maybe it should delete everything with #!/path/to/venv/python in it
+ logger.notify('Not deleting %s', bin_dir)
+
+ if hasattr(sys, 'real_prefix'):
+ logger.notify('Using real prefix %r' % sys.real_prefix)
+ prefix = sys.real_prefix
+ elif hasattr(sys, 'base_prefix'):
+ logger.notify('Using base prefix %r' % sys.base_prefix)
+ prefix = sys.base_prefix
+ else:
+ prefix = sys.prefix
+ mkdir(lib_dir)
+ fix_lib64(lib_dir, symlink)
+ stdlib_dirs = [os.path.dirname(os.__file__)]
+ if is_win:
+ stdlib_dirs.append(join(os.path.dirname(stdlib_dirs[0]), 'DLLs'))
+ elif is_darwin:
+ stdlib_dirs.append(join(stdlib_dirs[0], 'site-packages'))
+ if hasattr(os, 'symlink'):
+ logger.info('Symlinking Python bootstrap modules')
+ else:
+ logger.info('Copying Python bootstrap modules')
+ logger.indent += 2
+ try:
+ # copy required files...
+ for stdlib_dir in stdlib_dirs:
+ if not os.path.isdir(stdlib_dir):
+ continue
+ for fn in os.listdir(stdlib_dir):
+ bn = os.path.splitext(fn)[0]
+ if fn != 'site-packages' and bn in REQUIRED_FILES:
+ copyfile(join(stdlib_dir, fn), join(lib_dir, fn), symlink)
+ # ...and modules
+ copy_required_modules(home_dir, symlink)
+ finally:
+ logger.indent -= 2
+ mkdir(join(lib_dir, 'site-packages'))
+ import site
+ site_filename = site.__file__
+ if site_filename.endswith('.pyc'):
+ site_filename = site_filename[:-1]
+ elif site_filename.endswith('$py.class'):
+ site_filename = site_filename.replace('$py.class', '.py')
+ site_filename_dst = change_prefix(site_filename, home_dir)
+ site_dir = os.path.dirname(site_filename_dst)
+ writefile(site_filename_dst, SITE_PY)
+ writefile(join(site_dir, 'orig-prefix.txt'), prefix)
+ site_packages_filename = join(site_dir, 'no-global-site-packages.txt')
+ if not site_packages:
+ writefile(site_packages_filename, '')
+
+ if is_pypy or is_win:
+ stdinc_dir = join(prefix, 'include')
+ else:
+ stdinc_dir = join(prefix, 'include', py_version + abiflags)
+ if os.path.exists(stdinc_dir):
+ copyfile(stdinc_dir, inc_dir, symlink)
+ else:
+ logger.debug('No include dir %s' % stdinc_dir)
+
+ platinc_dir = distutils.sysconfig.get_python_inc(plat_specific=1)
+ if platinc_dir != stdinc_dir:
+ platinc_dest = distutils.sysconfig.get_python_inc(
+ plat_specific=1, prefix=home_dir)
+ if platinc_dir == platinc_dest:
+ # Do platinc_dest manually due to a CPython bug;
+ # not http://bugs.python.org/issue3386 but a close cousin
+ platinc_dest = subst_path(platinc_dir, prefix, home_dir)
+ if platinc_dest:
+ # PyPy's stdinc_dir and prefix are relative to the original binary
+ # (traversing virtualenvs), whereas the platinc_dir is relative to
+ # the inner virtualenv and ignores the prefix argument.
+ # This seems more evolved than designed.
+ copyfile(platinc_dir, platinc_dest, symlink)
+
+ # pypy never uses exec_prefix, just ignore it
+ if sys.exec_prefix != prefix and not is_pypy:
+ if is_win:
+ exec_dir = join(sys.exec_prefix, 'lib')
+ elif is_jython:
+ exec_dir = join(sys.exec_prefix, 'Lib')
+ else:
+ exec_dir = join(sys.exec_prefix, 'lib', py_version)
+ for fn in os.listdir(exec_dir):
+ copyfile(join(exec_dir, fn), join(lib_dir, fn), symlink)
+
+ if is_jython:
+ # Jython has either jython-dev.jar and javalib/ dir, or just
+ # jython.jar
+ for name in 'jython-dev.jar', 'javalib', 'jython.jar':
+ src = join(prefix, name)
+ if os.path.exists(src):
+ copyfile(src, join(home_dir, name), symlink)
+ # XXX: registry should always exist after Jython 2.5rc1
+ src = join(prefix, 'registry')
+ if os.path.exists(src):
+ copyfile(src, join(home_dir, 'registry'), symlink=False)
+ copyfile(join(prefix, 'cachedir'), join(home_dir, 'cachedir'),
+ symlink=False)
+
+ mkdir(bin_dir)
+ py_executable = join(bin_dir, os.path.basename(sys.executable))
+ if 'Python.framework' in prefix:
+ # OS X framework builds cause validation to break
+ # https://github.com/pypa/virtualenv/issues/322
+ if os.environ.get('__PYVENV_LAUNCHER__'):
+ del os.environ["__PYVENV_LAUNCHER__"]
+ if re.search(r'/Python(?:-32|-64)*$', py_executable):
+ # The name of the python executable is not quite what
+ # we want, rename it.
+ py_executable = os.path.join(
+ os.path.dirname(py_executable), 'python')
+
+ logger.notify('New %s executable in %s', expected_exe, py_executable)
+ pcbuild_dir = os.path.dirname(sys.executable)
+ pyd_pth = os.path.join(lib_dir, 'site-packages', 'virtualenv_builddir_pyd.pth')
+ if is_win and os.path.exists(os.path.join(pcbuild_dir, 'build.bat')):
+ logger.notify('Detected python running from build directory %s', pcbuild_dir)
+ logger.notify('Writing .pth file linking to build directory for *.pyd files')
+ writefile(pyd_pth, pcbuild_dir)
+ else:
+ pcbuild_dir = None
+ if os.path.exists(pyd_pth):
+ logger.info('Deleting %s (not Windows env or not build directory python)' % pyd_pth)
+ os.unlink(pyd_pth)
+
+ if sys.executable != py_executable:
+ ## FIXME: could I just hard link?
+ executable = sys.executable
+ shutil.copyfile(executable, py_executable)
+ make_exe(py_executable)
+ if is_win or is_cygwin:
+ pythonw = os.path.join(os.path.dirname(sys.executable), 'pythonw.exe')
+ if os.path.exists(pythonw):
+ logger.info('Also created pythonw.exe')
+ shutil.copyfile(pythonw, os.path.join(os.path.dirname(py_executable), 'pythonw.exe'))
+ python_d = os.path.join(os.path.dirname(sys.executable), 'python_d.exe')
+ python_d_dest = os.path.join(os.path.dirname(py_executable), 'python_d.exe')
+ if os.path.exists(python_d):
+ logger.info('Also created python_d.exe')
+ shutil.copyfile(python_d, python_d_dest)
+ elif os.path.exists(python_d_dest):
+ logger.info('Removed python_d.exe as it is no longer at the source')
+ os.unlink(python_d_dest)
+ # we need to copy the DLL to enforce that windows will load the correct one.
+ # may not exist if we are cygwin.
+ py_executable_dll = 'python%s%s.dll' % (
+ sys.version_info[0], sys.version_info[1])
+ py_executable_dll_d = 'python%s%s_d.dll' % (
+ sys.version_info[0], sys.version_info[1])
+ pythondll = os.path.join(os.path.dirname(sys.executable), py_executable_dll)
+ pythondll_d = os.path.join(os.path.dirname(sys.executable), py_executable_dll_d)
+ pythondll_d_dest = os.path.join(os.path.dirname(py_executable), py_executable_dll_d)
+ if os.path.exists(pythondll):
+ logger.info('Also created %s' % py_executable_dll)
+ shutil.copyfile(pythondll, os.path.join(os.path.dirname(py_executable), py_executable_dll))
+ if os.path.exists(pythondll_d):
+ logger.info('Also created %s' % py_executable_dll_d)
+ shutil.copyfile(pythondll_d, pythondll_d_dest)
+ elif os.path.exists(pythondll_d_dest):
+ logger.info('Removed %s as the source does not exist' % pythondll_d_dest)
+ os.unlink(pythondll_d_dest)
+ if is_pypy:
+ # make a symlink python --> pypy-c
+ python_executable = os.path.join(os.path.dirname(py_executable), 'python')
+ if sys.platform in ('win32', 'cygwin'):
+ python_executable += '.exe'
+ logger.info('Also created executable %s' % python_executable)
+ copyfile(py_executable, python_executable, symlink)
+
+ if is_win:
+ for name in ['libexpat.dll', 'libpypy.dll', 'libpypy-c.dll',
+ 'libeay32.dll', 'ssleay32.dll', 'sqlite3.dll',
+ 'tcl85.dll', 'tk85.dll']:
+ src = join(prefix, name)
+ if os.path.exists(src):
+ copyfile(src, join(bin_dir, name), symlink)
+
+ for d in sys.path:
+ if d.endswith('lib_pypy'):
+ break
+ else:
+ logger.fatal('Could not find lib_pypy in sys.path')
+ raise SystemExit(3)
+ logger.info('Copying lib_pypy')
+ copyfile(d, os.path.join(home_dir, 'lib_pypy'), symlink)
+
+ if os.path.splitext(os.path.basename(py_executable))[0] != expected_exe:
+ secondary_exe = os.path.join(os.path.dirname(py_executable),
+ expected_exe)
+ py_executable_ext = os.path.splitext(py_executable)[1]
+ if py_executable_ext.lower() == '.exe':
+ # python2.4 gives an extension of '.4' :P
+ secondary_exe += py_executable_ext
+ if os.path.exists(secondary_exe):
+ logger.warn('Not overwriting existing %s script %s (you must use %s)'
+ % (expected_exe, secondary_exe, py_executable))
+ else:
+ logger.notify('Also creating executable in %s' % secondary_exe)
+ shutil.copyfile(sys.executable, secondary_exe)
+ make_exe(secondary_exe)
+
+ if '.framework' in prefix:
+ if 'Python.framework' in prefix:
+ logger.debug('MacOSX Python framework detected')
+ # Make sure we use the embedded interpreter inside
+ # the framework, even if sys.executable points to
+ # the stub executable in ${sys.prefix}/bin
+ # See http://groups.google.com/group/python-virtualenv/
+ # browse_thread/thread/17cab2f85da75951
+ original_python = os.path.join(
+ prefix, 'Resources/Python.app/Contents/MacOS/Python')
+ if 'EPD' in prefix:
+ logger.debug('EPD framework detected')
+ original_python = os.path.join(prefix, 'bin/python')
+ shutil.copy(original_python, py_executable)
+
+ # Copy the framework's dylib into the virtual
+ # environment
+ virtual_lib = os.path.join(home_dir, '.Python')
+
+ if os.path.exists(virtual_lib):
+ os.unlink(virtual_lib)
+ copyfile(
+ os.path.join(prefix, 'Python'),
+ virtual_lib,
+ symlink)
+
+ # And then change the install_name of the copied python executable
+ try:
+ mach_o_change(py_executable,
+ os.path.join(prefix, 'Python'),
+ '@executable_path/../.Python')
+ except:
+ e = sys.exc_info()[1]
+ logger.warn("Could not call mach_o_change: %s. "
+ "Trying to call install_name_tool instead." % e)
+ try:
+ call_subprocess(
+ ["install_name_tool", "-change",
+ os.path.join(prefix, 'Python'),
+ '@executable_path/../.Python',
+ py_executable])
+ except:
+ logger.fatal("Could not call install_name_tool -- you must "
+ "have Apple's development tools installed")
+ raise
+
+ if not is_win:
+ # Ensure that 'python', 'pythonX' and 'pythonX.Y' all exist
+ py_exe_version_major = 'python%s' % sys.version_info[0]
+ py_exe_version_major_minor = 'python%s.%s' % (
+ sys.version_info[0], sys.version_info[1])
+ py_exe_no_version = 'python'
+ required_symlinks = [ py_exe_no_version, py_exe_version_major,
+ py_exe_version_major_minor ]
+
+ py_executable_base = os.path.basename(py_executable)
+
+ if py_executable_base in required_symlinks:
+ # Don't try to symlink to yourself.
+ required_symlinks.remove(py_executable_base)
+
+ for pth in required_symlinks:
+ full_pth = join(bin_dir, pth)
+ if os.path.exists(full_pth):
+ os.unlink(full_pth)
+ if symlink:
+ os.symlink(py_executable_base, full_pth)
+ else:
+ copyfile(py_executable, full_pth, symlink)
+
+ if is_win and ' ' in py_executable:
+ # There's a bug with subprocess on Windows when using a first
+ # argument that has a space in it. Instead we have to quote
+ # the value:
+ py_executable = '"%s"' % py_executable
+ # NOTE: keep this check as one line, cmd.exe doesn't cope with line breaks
+ cmd = [py_executable, '-c', 'import sys;out=sys.stdout;'
+ 'getattr(out, "buffer", out).write(sys.prefix.encode("utf-8"))']
+ logger.info('Testing executable with %s %s "%s"' % tuple(cmd))
+ try:
+ proc = subprocess.Popen(cmd,
+ stdout=subprocess.PIPE)
+ proc_stdout, proc_stderr = proc.communicate()
+ except OSError:
+ e = sys.exc_info()[1]
+ if e.errno == errno.EACCES:
+ logger.fatal('ERROR: The executable %s could not be run: %s' % (py_executable, e))
+ sys.exit(100)
+ else:
+ raise e
+
+ proc_stdout = proc_stdout.strip().decode("utf-8")
+ proc_stdout = os.path.normcase(os.path.abspath(proc_stdout))
+ norm_home_dir = os.path.normcase(os.path.abspath(home_dir))
+ if hasattr(norm_home_dir, 'decode'):
+ norm_home_dir = norm_home_dir.decode(sys.getfilesystemencoding())
+ if proc_stdout != norm_home_dir:
+ logger.fatal(
+ 'ERROR: The executable %s is not functioning' % py_executable)
+ logger.fatal(
+ 'ERROR: It thinks sys.prefix is %r (should be %r)'
+ % (proc_stdout, norm_home_dir))
+ logger.fatal(
+ 'ERROR: virtualenv is not compatible with this system or executable')
+ if is_win:
+ logger.fatal(
+ 'Note: some Windows users have reported this error when they '
+ 'installed Python for "Only this user" or have multiple '
+ 'versions of Python installed. Copying the appropriate '
+ 'PythonXX.dll to the virtualenv Scripts/ directory may fix '
+ 'this problem.')
+ sys.exit(100)
+ else:
+ logger.info('Got sys.prefix result: %r' % proc_stdout)
+
+ pydistutils = os.path.expanduser('~/.pydistutils.cfg')
+ if os.path.exists(pydistutils):
+ logger.notify('Please make sure you remove any previous custom paths from '
+ 'your %s file.' % pydistutils)
+ ## FIXME: really this should be calculated earlier
+
+ fix_local_scheme(home_dir, symlink)
+
+ if site_packages:
+ if os.path.exists(site_packages_filename):
+ logger.info('Deleting %s' % site_packages_filename)
+ os.unlink(site_packages_filename)
+
+ return py_executable
+
+
+def install_activate(home_dir, bin_dir, prompt=None):
+ home_dir = os.path.abspath(home_dir)
+ if is_win or is_jython and os._name == 'nt':
+ files = {
+ 'activate.bat': ACTIVATE_BAT,
+ 'deactivate.bat': DEACTIVATE_BAT,
+ 'activate.ps1': ACTIVATE_PS,
+ }
+
+ # MSYS needs paths of the form /c/path/to/file
+ drive, tail = os.path.splitdrive(home_dir.replace(os.sep, '/'))
+ home_dir_msys = (drive and "/%s%s" or "%s%s") % (drive[:1], tail)
+
+ # Run-time conditional enables (basic) Cygwin compatibility
+ home_dir_sh = ("""$(if [ "$OSTYPE" "==" "cygwin" ]; then cygpath -u '%s'; else echo '%s'; fi;)""" %
+ (home_dir, home_dir_msys))
+ files['activate'] = ACTIVATE_SH.replace('__VIRTUAL_ENV__', home_dir_sh)
+
+ else:
+ files = {'activate': ACTIVATE_SH}
+
+ # suppling activate.fish in addition to, not instead of, the
+ # bash script support.
+ files['activate.fish'] = ACTIVATE_FISH
+
+ # same for csh/tcsh support...
+ files['activate.csh'] = ACTIVATE_CSH
+
+ files['activate_this.py'] = ACTIVATE_THIS
+ if hasattr(home_dir, 'decode'):
+ home_dir = home_dir.decode(sys.getfilesystemencoding())
+ vname = os.path.basename(home_dir)
+ for name, content in files.items():
+ content = content.replace('__VIRTUAL_PROMPT__', prompt or '')
+ content = content.replace('__VIRTUAL_WINPROMPT__', prompt or '(%s)' % vname)
+ content = content.replace('__VIRTUAL_ENV__', home_dir)
+ content = content.replace('__VIRTUAL_NAME__', vname)
+ content = content.replace('__BIN_NAME__', os.path.basename(bin_dir))
+ writefile(os.path.join(bin_dir, name), content)
+
+def install_distutils(home_dir):
+ distutils_path = change_prefix(distutils.__path__[0], home_dir)
+ mkdir(distutils_path)
+ ## FIXME: maybe this prefix setting should only be put in place if
+ ## there's a local distutils.cfg with a prefix setting?
+ home_dir = os.path.abspath(home_dir)
+ ## FIXME: this is breaking things, removing for now:
+ #distutils_cfg = DISTUTILS_CFG + "\n[install]\nprefix=%s\n" % home_dir
+ writefile(os.path.join(distutils_path, '__init__.py'), DISTUTILS_INIT)
+ writefile(os.path.join(distutils_path, 'distutils.cfg'), DISTUTILS_CFG, overwrite=False)
+
+def fix_local_scheme(home_dir, symlink=True):
+ """
+ Platforms that use the "posix_local" install scheme (like Ubuntu with
+ Python 2.7) need to be given an additional "local" location, sigh.
+ """
+ try:
+ import sysconfig
+ except ImportError:
+ pass
+ else:
+ if sysconfig._get_default_scheme() == 'posix_local':
+ local_path = os.path.join(home_dir, 'local')
+ if not os.path.exists(local_path):
+ os.mkdir(local_path)
+ for subdir_name in os.listdir(home_dir):
+ if subdir_name == 'local':
+ continue
+ copyfile(os.path.abspath(os.path.join(home_dir, subdir_name)), \
+ os.path.join(local_path, subdir_name), symlink)
+
+def fix_lib64(lib_dir, symlink=True):
+ """
+ Some platforms (particularly Gentoo on x64) put things in lib64/pythonX.Y
+ instead of lib/pythonX.Y. If this is such a platform we'll just create a
+ symlink so lib64 points to lib
+ """
+ if [p for p in distutils.sysconfig.get_config_vars().values()
+ if isinstance(p, basestring) and 'lib64' in p]:
+ # PyPy's library path scheme is not affected by this.
+ # Return early or we will die on the following assert.
+ if is_pypy:
+ logger.debug('PyPy detected, skipping lib64 symlinking')
+ return
+
+ logger.debug('This system uses lib64; symlinking lib64 to lib')
+
+ assert os.path.basename(lib_dir) == 'python%s' % sys.version[:3], (
+ "Unexpected python lib dir: %r" % lib_dir)
+ lib_parent = os.path.dirname(lib_dir)
+ top_level = os.path.dirname(lib_parent)
+ lib_dir = os.path.join(top_level, 'lib')
+ lib64_link = os.path.join(top_level, 'lib64')
+ assert os.path.basename(lib_parent) == 'lib', (
+ "Unexpected parent dir: %r" % lib_parent)
+ if os.path.lexists(lib64_link):
+ return
+ if symlink:
+ os.symlink('lib', lib64_link)
+ else:
+ copyfile('lib', lib64_link)
+
+def resolve_interpreter(exe):
+ """
+ If the executable given isn't an absolute path, search $PATH for the interpreter
+ """
+ # If the "executable" is a version number, get the installed executable for
+ # that version
+ python_versions = get_installed_pythons()
+ if exe in python_versions:
+ exe = python_versions[exe]
+
+ if os.path.abspath(exe) != exe:
+ paths = os.environ.get('PATH', '').split(os.pathsep)
+ for path in paths:
+ if os.path.exists(os.path.join(path, exe)):
+ exe = os.path.join(path, exe)
+ break
+ if not os.path.exists(exe):
+ logger.fatal('The executable %s (from --python=%s) does not exist' % (exe, exe))
+ raise SystemExit(3)
+ if not is_executable(exe):
+ logger.fatal('The executable %s (from --python=%s) is not executable' % (exe, exe))
+ raise SystemExit(3)
+ return exe
+
+def is_executable(exe):
+ """Checks a file is executable"""
+ return os.access(exe, os.X_OK)
+
+############################################################
+## Relocating the environment:
+
+def make_environment_relocatable(home_dir):
+ """
+ Makes the already-existing environment use relative paths, and takes out
+ the #!-based environment selection in scripts.
+ """
+ home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir)
+ activate_this = os.path.join(bin_dir, 'activate_this.py')
+ if not os.path.exists(activate_this):
+ logger.fatal(
+ 'The environment doesn\'t have a file %s -- please re-run virtualenv '
+ 'on this environment to update it' % activate_this)
+ fixup_scripts(home_dir, bin_dir)
+ fixup_pth_and_egg_link(home_dir)
+ ## FIXME: need to fix up distutils.cfg
+
+OK_ABS_SCRIPTS = ['python', 'python%s' % sys.version[:3],
+ 'activate', 'activate.bat', 'activate_this.py',
+ 'activate.fish', 'activate.csh']
+
+def fixup_scripts(home_dir, bin_dir):
+ if is_win:
+ new_shebang_args = (
+ '%s /c' % os.path.normcase(os.environ.get('COMSPEC', 'cmd.exe')),
+ '', '.exe')
+ else:
+ new_shebang_args = ('/usr/bin/env', sys.version[:3], '')
+
+ # This is what we expect at the top of scripts:
+ shebang = '#!%s' % os.path.normcase(os.path.join(
+ os.path.abspath(bin_dir), 'python%s' % new_shebang_args[2]))
+ # This is what we'll put:
+ new_shebang = '#!%s python%s%s' % new_shebang_args
+
+ for filename in os.listdir(bin_dir):
+ filename = os.path.join(bin_dir, filename)
+ if not os.path.isfile(filename):
+ # ignore subdirs, e.g. .svn ones.
+ continue
+ f = open(filename, 'rb')
+ try:
+ try:
+ lines = f.read().decode('utf-8').splitlines()
+ except UnicodeDecodeError:
+ # This is probably a binary program instead
+ # of a script, so just ignore it.
+ continue
+ finally:
+ f.close()
+ if not lines:
+ logger.warn('Script %s is an empty file' % filename)
+ continue
+
+ old_shebang = lines[0].strip()
+ old_shebang = old_shebang[0:2] + os.path.normcase(old_shebang[2:])
+
+ if not old_shebang.startswith(shebang):
+ if os.path.basename(filename) in OK_ABS_SCRIPTS:
+ logger.debug('Cannot make script %s relative' % filename)
+ elif lines[0].strip() == new_shebang:
+ logger.info('Script %s has already been made relative' % filename)
+ else:
+ logger.warn('Script %s cannot be made relative (it\'s not a normal script that starts with %s)'
+ % (filename, shebang))
+ continue
+ logger.notify('Making script %s relative' % filename)
+ script = relative_script([new_shebang] + lines[1:])
+ f = open(filename, 'wb')
+ f.write('\n'.join(script).encode('utf-8'))
+ f.close()
+
+def relative_script(lines):
+ "Return a script that'll work in a relocatable environment."
+ activate = "import os; activate_this=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'activate_this.py'); exec(compile(open(activate_this).read(), activate_this, 'exec'), dict(__file__=activate_this)); del os, activate_this"
+ # Find the last future statement in the script. If we insert the activation
+ # line before a future statement, Python will raise a SyntaxError.
+ activate_at = None
+ for idx, line in reversed(list(enumerate(lines))):
+ if line.split()[:3] == ['from', '__future__', 'import']:
+ activate_at = idx + 1
+ break
+ if activate_at is None:
+ # Activate after the shebang.
+ activate_at = 1
+ return lines[:activate_at] + ['', activate, ''] + lines[activate_at:]
+
+def fixup_pth_and_egg_link(home_dir, sys_path=None):
+ """Makes .pth and .egg-link files use relative paths"""
+ home_dir = os.path.normcase(os.path.abspath(home_dir))
+ if sys_path is None:
+ sys_path = sys.path
+ for path in sys_path:
+ if not path:
+ path = '.'
+ if not os.path.isdir(path):
+ continue
+ path = os.path.normcase(os.path.abspath(path))
+ if not path.startswith(home_dir):
+ logger.debug('Skipping system (non-environment) directory %s' % path)
+ continue
+ for filename in os.listdir(path):
+ filename = os.path.join(path, filename)
+ if filename.endswith('.pth'):
+ if not os.access(filename, os.W_OK):
+ logger.warn('Cannot write .pth file %s, skipping' % filename)
+ else:
+ fixup_pth_file(filename)
+ if filename.endswith('.egg-link'):
+ if not os.access(filename, os.W_OK):
+ logger.warn('Cannot write .egg-link file %s, skipping' % filename)
+ else:
+ fixup_egg_link(filename)
+
+def fixup_pth_file(filename):
+ lines = []
+ prev_lines = []
+ f = open(filename)
+ prev_lines = f.readlines()
+ f.close()
+ for line in prev_lines:
+ line = line.strip()
+ if (not line or line.startswith('#') or line.startswith('import ')
+ or os.path.abspath(line) != line):
+ lines.append(line)
+ else:
+ new_value = make_relative_path(filename, line)
+ if line != new_value:
+ logger.debug('Rewriting path %s as %s (in %s)' % (line, new_value, filename))
+ lines.append(new_value)
+ if lines == prev_lines:
+ logger.info('No changes to .pth file %s' % filename)
+ return
+ logger.notify('Making paths in .pth file %s relative' % filename)
+ f = open(filename, 'w')
+ f.write('\n'.join(lines) + '\n')
+ f.close()
+
+def fixup_egg_link(filename):
+ f = open(filename)
+ link = f.readline().strip()
+ f.close()
+ if os.path.abspath(link) != link:
+ logger.debug('Link in %s already relative' % filename)
+ return
+ new_link = make_relative_path(filename, link)
+ logger.notify('Rewriting link %s in %s as %s' % (link, filename, new_link))
+ f = open(filename, 'w')
+ f.write(new_link)
+ f.close()
+
+def make_relative_path(source, dest, dest_is_directory=True):
+ """
+ Make a filename relative, where the filename is dest, and it is
+ being referred to from the filename source.
+
+ >>> make_relative_path('/usr/share/something/a-file.pth',
+ ... '/usr/share/another-place/src/Directory')
+ '../another-place/src/Directory'
+ >>> make_relative_path('/usr/share/something/a-file.pth',
+ ... '/home/user/src/Directory')
+ '../../../home/user/src/Directory'
+ >>> make_relative_path('/usr/share/a-file.pth', '/usr/share/')
+ './'
+ """
+ source = os.path.dirname(source)
+ if not dest_is_directory:
+ dest_filename = os.path.basename(dest)
+ dest = os.path.dirname(dest)
+ dest = os.path.normpath(os.path.abspath(dest))
+ source = os.path.normpath(os.path.abspath(source))
+ dest_parts = dest.strip(os.path.sep).split(os.path.sep)
+ source_parts = source.strip(os.path.sep).split(os.path.sep)
+ while dest_parts and source_parts and dest_parts[0] == source_parts[0]:
+ dest_parts.pop(0)
+ source_parts.pop(0)
+ full_parts = ['..']*len(source_parts) + dest_parts
+ if not dest_is_directory:
+ full_parts.append(dest_filename)
+ if not full_parts:
+ # Special case for the current directory (otherwise it'd be '')
+ return './'
+ return os.path.sep.join(full_parts)
+
+
+
+############################################################
+## Bootstrap script creation:
+
+def create_bootstrap_script(extra_text, python_version=''):
+ """
+ Creates a bootstrap script, which is like this script but with
+ extend_parser, adjust_options, and after_install hooks.
+
+ This returns a string that (written to disk of course) can be used
+ as a bootstrap script with your own customizations. The script
+ will be the standard virtualenv.py script, with your extra text
+ added (your extra text should be Python code).
+
+ If you include these functions, they will be called:
+
+ ``extend_parser(optparse_parser)``:
+ You can add or remove options from the parser here.
+
+ ``adjust_options(options, args)``:
+ You can change options here, or change the args (if you accept
+ different kinds of arguments, be sure you modify ``args`` so it is
+ only ``[DEST_DIR]``).
+
+ ``after_install(options, home_dir)``:
+
+ After everything is installed, this function is called. This
+ is probably the function you are most likely to use. An
+ example would be::
+
+ def after_install(options, home_dir):
+ subprocess.call([join(home_dir, 'bin', 'easy_install'),
+ 'MyPackage'])
+ subprocess.call([join(home_dir, 'bin', 'my-package-script'),
+ 'setup', home_dir])
+
+ This example immediately installs a package, and runs a setup
+ script from that package.
+
+ If you provide something like ``python_version='2.5'`` then the
+ script will start with ``#!/usr/bin/env python2.5`` instead of
+ ``#!/usr/bin/env python``. You can use this when the script must
+ be run with a particular Python version.
+ """
+ filename = __file__
+ if filename.endswith('.pyc'):
+ filename = filename[:-1]
+ f = codecs.open(filename, 'r', encoding='utf-8')
+ content = f.read()
+ f.close()
+ py_exe = 'python%s' % python_version
+ content = (('#!/usr/bin/env %s\n' % py_exe)
+ + '## WARNING: This file is generated\n'
+ + content)
+ return content.replace('##EXT' 'END##', extra_text)
+
+###############################################################################
+## 'prefix code' START
+# requirements from normal_installation.txt
+NORMAL_INSTALLATION = ['click', 'dragonlib', 'MC6809', 'DragonPy']
+
+# requirements from git_readonly_installation.txt
+GIT_READONLY_INSTALLATION = ['click',
+ 'docutils',
+ 'virtualenv',
+ '--editable=git+https://github.com/6809/dragonlib.git#egg=dragonlib',
+ '--editable=git+https://github.com/6809/MC6809.git#egg=MC6809',
+ '--editable=git+https://github.com/jedie/bootstrap_env.git#egg=bootstrap_env',
+ '--editable=git+https://github.com:jedie/DragonPy.git#egg=DragonPy']
+
+# requirements from developer_installation.txt
+DEVELOPER_INSTALLATION = ['click',
+ 'docutils',
+ 'wheel',
+ 'virtualenv',
+ '--editable=git+git@github.com:6809/dragonlib.git#egg=dragonlib',
+ '--editable=git+git@github.com:6809/MC6809.git#egg=MC6809',
+ '--editable=git+git@github.com:jedie/bootstrap_env.git#egg=bootstrap_env',
+ '--editable=git+git@github.com:jedie/DragonPy.git#egg=DragonPy']
+###############################################################################
+## '.../src/dragonpy/bootstrap/source_prefix_code.py' START
+# For choosing the installation type:
+INST_PYPI="pypi"
+INST_GIT="git_readonly"
+INST_DEV="dev"
+
+INST_TYPES=(INST_PYPI, INST_GIT, INST_DEV)
+## '.../src/dragonpy/bootstrap/source_prefix_code.py' END
+###############################################################################
+## 'prefix code' END
+###############################################################################
+###############################################################################
+## '.../src/bootstrap-env/bootstrap_env/bootstrap_install_pip.py' START
+INSTALL_PIP_OPTION="--install-pip"
+
+
+class EnvSubprocess(object):
+ """
+ Use to install pip and useful also to install other packages in after_install.
+ """
+ def __init__(self, home_dir):
+ self.abs_home_dir = os.path.abspath(home_dir)
+
+ if sys.platform in ['win32','cygwin','win64']:
+ self.bin_dir = os.path.join(self.abs_home_dir, "Scripts")
+ else:
+ self.bin_dir = os.path.join(self.abs_home_dir, "bin")
+
+ self.python_cmd = os.path.join(self.bin_dir, "python")
+ self.pip_cmd = os.path.join(self.bin_dir, "pip")
+
+ self.subprocess_defaults = {
+ "cwd": self.bin_dir,
+ "env": {
+ "VIRTUAL_ENV": self.abs_home_dir,
+ "PATH": self.bin_dir + os.pathsep + os.environ["PATH"],
+ }
+ }
+ try:
+ # Work-a-round for http://bugs.python.org/issue20614 :
+ # Python3 will crash under windows without SYSTEMROOT
+ self.subprocess_defaults["env"]["SYSTEMROOT"] = os.environ['SYSTEMROOT']
+ except KeyError:
+ pass
+
+ def _subprocess(self, cmd):
+ print("call %r" % " ".join(cmd))
+ subprocess.call(cmd, **self.subprocess_defaults)
+
+ def call_env_python(self, cmd):
+ self._subprocess([self.python_cmd] + cmd)
+
+ def call_env_pip(self, cmd):
+ self._subprocess([self.pip_cmd] + cmd)
+
+
+def _install_pip(options, home_dir):
+ print("Install pip...")
+ bootstrap_file = os.path.abspath(sys.argv[0])
+ assert os.path.isfile(bootstrap_file), "Path to self not found?!?! (%r not exists?!?!)" % bootstrap_file
+
+ env_subprocess = EnvSubprocess(home_dir)
+ env_subprocess.call_env_python([bootstrap_file, "--install-pip", env_subprocess.abs_home_dir])
+
+
+def extend_parser(parser):
+ parser.add_option(
+ INSTALL_PIP_OPTION,
+ dest='install_pip',
+ help="Only for internal usage!"
+ )
+ if INSTALL_PIP_OPTION in sys.argv:
+ return # Skip the additional code, if pip should be installed
+
+
+ ###############################################################################
+ ## '.../src/dragonpy/bootstrap/source_extend_parser.py' START
+ parser.add_option("--install_type", dest="install_type", choices=INST_TYPES,
+ help="Install type: %s (See README!)" % ", ".join(INST_TYPES)
+ )
+ ## '.../src/dragonpy/bootstrap/source_extend_parser.py' END
+ ###############################################################################
+
+
+def adjust_options(options, args):
+ # Importand, otherwise it failed with 'ImportError: No module named pip'
+ # because the wheel files are not there
+ options.no_setuptools=True
+
+ if options.install_pip:
+ print("install pip from self contained 'get_pip.py'")
+ sys.argv = [sys.argv[0]]
+ get_pip() # renamed main() from 'get_pip.py', it exists in the generated bootstrap file!
+ print("pip is installed.")
+ sys.exit(0)
+
+
+ ###############################################################################
+ ## '.../src/dragonpy/bootstrap/source_adjust_options.py' START
+ if options.install_type == None:
+ sys.stderr.write("\n\nERROR:\nYou must add --install_type option (See README) !\n")
+ sys.stderr.write("Available types: %s\n\n" % ", ".join(INST_TYPES))
+ sys.exit(-1)
+
+ sys.stdout.write("\nInstall type: %r\n" % options.install_type)
+ ## '.../src/dragonpy/bootstrap/source_adjust_options.py' END
+ ###############################################################################
+
+
+def after_install(options, home_dir):
+ _install_pip(options, home_dir)
+## '.../src/bootstrap-env/bootstrap_env/bootstrap_install_pip.py' END
+###############################################################################
+ ###############################################################################
+ ## '.../src/dragonpy/bootstrap/source_after_install.py' START
+ """
+ called after virtualenv was created and pip/setuptools installed.
+ Now we installed requirement libs/packages.
+ """
+ if options.install_type==INST_PYPI:
+ requirements=NORMAL_INSTALLATION
+ elif options.install_type==INST_GIT:
+ requirements=GIT_READONLY_INSTALLATION
+ elif options.install_type==INST_DEV:
+ requirements=DEVELOPER_INSTALLATION
+ else:
+ # Should never happen
+ raise RuntimeError("Install type %r unknown?!?" % options.install_type)
+
+ env_subprocess = EnvSubprocess(home_dir) # from bootstrap_env.bootstrap_install_pip
+
+ logfile = os.path.join(env_subprocess.abs_home_dir, "install.log")
+
+ for requirement in requirements:
+ sys.stdout.write("\n\nInstall %r:\n" % requirement)
+ env_subprocess.call_env_pip(["install", "--log=%s" % logfile, requirement])
+ sys.stdout.write("\n")
+ ## '.../src/dragonpy/bootstrap/source_after_install.py' END
+ ###############################################################################
+
+
+
+
+###############################################################################
+## 'get_pip.py' START
+#!/usr/bin/env python
+#
+# Hi There!
+# You may be wondering what this giant blob of binary data here is, you might
+# even be worried that we're up to something nefarious (good for you for being
+# paranoid!). This is a base85 encoding of a zip file, this zip file contains
+# an entire copy of pip.
+#
+# Pip is a thing that installs packages, pip itself is a package that someone
+# might want to install, especially if they're looking to run this get-pip.py
+# script. Pip has a lot of code to deal with the security of installing
+# packages, various edge cases on various platforms, and other such sort of
+# "tribal knowledge" that has been encoded in its code base. Because of this
+# we basically include an entire copy of pip inside this blob. We do this
+# because the alternatives are attempt to implement a "minipip" that probably
+# doesn't do things correctly and has weird edge cases, or compress pip itself
+# down into a single file.
+#
+# If you're wondering how this is created, it is using an invoke task located
+# in tasks/generate.py called "installer". It can be invoked by using
+# ``invoke generate.installer``.
+
+import os.path
+import pkgutil
+import shutil
+import sys
+import struct
+import tempfile
+
+# Useful for very coarse version differentiation.
+PY2 = sys.version_info[0] == 2
+PY3 = sys.version_info[0] == 3
+
+if PY3:
+ iterbytes = iter
+else:
+ def iterbytes(buf):
+ return (ord(byte) for byte in buf)
+
+try:
+ from base64 import b85decode
+except ImportError:
+ _b85alphabet = (b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ b"abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~")
+
+ def b85decode(b):
+ _b85dec = [None] * 256
+ for i, c in enumerate(iterbytes(_b85alphabet)):
+ _b85dec[c] = i
+
+ padding = (-len(b)) % 5
+ b = b + b'~' * padding
+ out = []
+ packI = struct.Struct('!I').pack
+ for i in range(0, len(b), 5):
+ chunk = b[i:i + 5]
+ acc = 0
+ try:
+ for c in iterbytes(chunk):
+ acc = acc * 85 + _b85dec[c]
+ except TypeError:
+ for j, c in enumerate(iterbytes(chunk)):
+ if _b85dec[c] is None:
+ raise ValueError(
+ 'bad base85 character at position %d' % (i + j)
+ )
+ raise
+ try:
+ out.append(packI(acc))
+ except struct.error:
+ raise ValueError('base85 overflow in hunk starting at byte %d'
+ % i)
+
+ result = b''.join(out)
+ if padding:
+ result = result[:-padding]
+ return result
+
+
+def bootstrap(tmpdir=None):
+ # Import pip so we can use it to install pip and maybe setuptools too
+ import pip
+ from pip.commands.install import InstallCommand
+
+ # Wrapper to provide default certificate with the lowest priority
+ class CertInstallCommand(InstallCommand):
+ def parse_args(self, args):
+ # If cert isn't specified in config or environment, we provide our
+ # own certificate through defaults.
+ # This allows user to specify custom cert anywhere one likes:
+ # config, environment variable or argv.
+ if not self.parser.get_default_values().cert:
+ self.parser.defaults["cert"] = cert_path # calculated below
+ return super(CertInstallCommand, self).parse_args(args)
+
+ pip.commands_dict["install"] = CertInstallCommand
+
+ # We always want to install pip
+ packages = ["pip"]
+
+ # Check if the user has requested us not to install setuptools
+ if "--no-setuptools" in sys.argv or os.environ.get("PIP_NO_SETUPTOOLS"):
+ args = [x for x in sys.argv[1:] if x != "--no-setuptools"]
+ else:
+ args = sys.argv[1:]
+
+ # We want to see if setuptools is available before attempting to
+ # install it
+ try:
+ import setuptools # noqa
+ except ImportError:
+ packages += ["setuptools"]
+
+ # Check if the user has requested us not to install wheel
+ if "--no-wheel" in args or os.environ.get("PIP_NO_WHEEL"):
+ args = [x for x in args if x != "--no-wheel"]
+ else:
+ # We want to see if wheel is available before attempting to install it.
+ try:
+ import wheel # noqa
+ except ImportError:
+ args += ["wheel"]
+
+ delete_tmpdir = False
+ try:
+ # Create a temporary directory to act as a working directory if we were
+ # not given one.
+ if tmpdir is None:
+ tmpdir = tempfile.mkdtemp()
+ delete_tmpdir = True
+
+ # We need to extract the SSL certificates from requests so that they
+ # can be passed to --cert
+ cert_path = os.path.join(tmpdir, "cacert.pem")
+ with open(cert_path, "wb") as cert:
+ cert.write(pkgutil.get_data("pip._vendor.requests", "cacert.pem"))
+
+ # Execute the included pip and use it to install the latest pip and
+ # setuptools from PyPI
+ sys.exit(pip.main(["install", "--upgrade"] + packages + args))
+ finally:
+ # Remove our temporary directory
+ if delete_tmpdir and tmpdir:
+ shutil.rmtree(tmpdir, ignore_errors=True)
+
+
+def get_pip():
+ tmpdir = None
+ try:
+ # Create a temporary working directory
+ tmpdir = tempfile.mkdtemp()
+
+ # Unpack the zipfile into the temporary directory
+ pip_zip = os.path.join(tmpdir, "pip.zip")
+ with open(pip_zip, "wb") as fp:
+ fp.write(b85decode(DATA.replace(b"\n", b"")))
+
+ # Add the zipfile to sys.path so that we can import it
+ sys.path.insert(0, pip_zip)
+
+ # Run the bootstrap
+ bootstrap(tmpdir=tmpdir)
+ finally:
+ # Clean up our temporary working directory
+ if tmpdir:
+ shutil.rmtree(tmpdir, ignore_errors=True)
+
+
+DATA = b"""
+P)h>@6aWAK2mtS)wno;)icgje006Ei000jF003}la4%n9X>MtBUtcb8d7WBuZ`-yK|KFd2brwikWo_B
+;u!rn<*pQ}JfvyeG_Vod&A;=bObCD&Dl#;mHzWcj7k|JfvSvC!;CGmK7Jl_3ycgo4LuUaL)T8i>3Uf!
+{K-)yULvX<43rRlnDTFKLtiCtaEhGk1t6>Y;){XChN_eHhYh;m~eE7jfAO`S=_?el#mOCVI;OttT5C7
+)=ywWt&Ru;O(is#00muS(TqMUmmlODQWEvx{oC%gWq5U5T3R9Fw*YMK^!Ln^b5XJWq3>8Yz}7iHK>im
+euCS+?>~vuSm3`xLY~iqKVm#%T+5yR>VxT%R4R=kjHG9ea7%&OvOsdBN9NTQurtUeqruxyzb{dn;XOOY|12T6iY~H_KCECyGp_mh|{!`wT`}HI6L3<7HmSMDpK
+St{Rop+3GgiaFw*OD8%yHkxdIH3@+F@4yNJPdge#%1o0%AOeQBRQ%Y>g9WNWUt|VI**)9J!Ybv(nY@5
+~f9*N#>g<@;*z!l3_4crO=YisuGe#=WE4S2FUk%8apXYkt@aA)_NW#C*VOq8O5{SgW&n=Qv>v0at71&
+`U(F0?blF0b@zrVNBjT!IpJ$Ox>%Kr;@T1iRXxviFs|W#B?Jm&G0=0sjo#TQn`XO=7*A4Bl~`xLYRvniMxBNaSuZbLQ!*LY3u!Za_MBV(C;pV+8Q)}jEnw2$Ew_O+6H8Z(F;zNzGXKJE(W2svM~tZgdrzPjKI52JH
+(p6PUI;+$5hjl&ET-lEf>rn?L*V}?y&g>Ht5h#S5Tjlu}++`dPZOo;BX%$5ab%RN(7D_6MWs^qL%lPF
+KR+VY}cY9&PtY(t3ZEdzx
+gxNc>BxM>&y3-0XZP9TqOYDLRO`=8(tE8M3(fp0td~}$sFBHfK1YlZ?9jx3l@p03(#?apptr%_ra8NT;Gpe>Uj$jiH;T0$+=Y=A
+b%+D
+bP9#hMm;|(g*i-zR1>mQ3;Bx}M1K;T}9B0brb2Htsr&aC2D;~gS&Z47Y0c$a
+tN%lw^rM3HVnK*5aOZ~P{K*RoOfqS=PQEQtErI^mr|*sEBf`3;kzy6-G+FH{s&w!U^Gj|gd@X;l{c`F
+q*&0}afga7uE^_rAKtDx%fcF&oq-gdik`ta^(|@Hi^-&@$-je}ZxWxFnr~xa)cYy6G`n;}$I8M90Hy0
+J42cuHD3;TE49wA;YVdh%HDQ4aR-v8i^y^Fh5PXIdGMQw}Tc4p2Nn6@PKDt3co3CUAYOP7)g>clN&Wu
+#I6ZQ`jMaLAWby;6g@qK#g6aOnnYj_v1juxEl%EjxVpN&eD^N>`SSJEV&a97K1x~DLdqcdov2y`5k$e
+Y7QDj&eGq4N8RT|&U>!y?&c(G73_ci)59UDxs71?q=((B0>czl(XuXG+-9{l`fEf!!EKSp>18&+h8;XI19tvO-r(V7&{)XhR9Mm07?waf}%B5DOAw(hpG{yfNuh9zQdRIXEdeH|`vsONpTZ41gji!<_
+Nlydpv8upP5mBX%6Jnfbqhm<2Y4huhfVzX|tILQd@)_Ftai2)qfD!s>wm?ekl58K
+j_1VK;6U%=!wahTPG%Q-4mvRU71IlT=3EjM>u%qr2^JOhf>D%qkfh1Ap}?*Jqpf&j`#0uwBKfYm1<}lrnp@c{LY4M8lD(EE$o<)jo?TU=h!E)V%G+?6m?G=W3zU2JGi~029`0QY2
+#I`WG`F~pMA8SA&^QRKHXqy+LyQh?{)Ix5|X})pWeTE`{tdK-L*c!?*R97i4#t9of=K!G0xov>+GhLT
+0xJpV{6*r3X7y>3rF#0%XAA@nZwU1XkgDuNj04f!hUOYGSasjID$n&ca-^kF8jF!`_k}g0CrqTgJILl
+>+84I=WrNLAs~QufOn4eQM|LQfMY#?E^b5XPxAvWwo5ZfoQ(lf(39JBB=m9O3%uqpE)&
+iA;4Be27vWaR-bu!txHJ!{t;HoU-_y5e>a=E#%~#LWT$DUc(IO|-XN$tW5#pQpmWEhKFn70F!B%@CnN
+@3JHYJ*fCapVfBUa3Qy8rN-d8l68-aJ8V>yb7o8CT%sggt
+x4o~TCfWNT5FWUA*~Lt4*ub?(p&^cq}`lKvf;*f3QZ=@Po{jRLZ8{PP7!b!kwixs?9WGvuO}7(*s-Go
+8=Dec<2ubP=;pgM|f`4=@AMn=_hA2^+El_k~H{cbc5a?{$A1Z!tq0^3uv^TbMLeL*11Y*+9er)`bBK2SZ=M4?u3-_$LG;@W9-o
+4qJAxPkVE6}%u_qTE4|rr*Xu;+P*iNnYy))-jokL
+_E<>#kOk1LOtA1C7r(LC=kGAv=D`)+6BERWm7u?l`aRevJBxVZcarjKM5jy1w=aH@VhRVRG2nVSaNQC
+)ohKOSVD@$o?fBoL)DqVbP~c)7PWt1pz!-D+83TAe7W{EDhE$zy@j?2@P+p(fW8uD-)G!(RQ;Jo
+xa*Q)S2yfc3lX+tL7NbPX@O5j4F#4!HT9nzQTr*?ebLnXr9oHuQDp|X_x}MtB6LPlNBO;hh9ZachS)9m2Y!fHTg19=I-g)>-5)UZy*_y
+g#11e(nC2o%NFCruN3-zSHly^kvv&4T)DAYEvR0g_9P5B)m%Bk#ZkBv#&T8)v!gq=!%TaNnmBwC!^Py
+mm5=^JNe<-@X_TBwIZv^)H;@Wb1=4di?Plpn;|NU2C%)Ny8rJE~
+SiK$#S29>XbnQuoOn|gWaxXKSuO9KQH000080Pmr;MpT|QaPA8L03|5^01^NI0B~t=FJfVHWn*t`ZDD
+R?E^v9x8f%Z+HuC%a3PQ!gQs5{}4+k6;FnY=ECN-Mci+wabPechs<(d(5lRspod(jDk)fbXizeKUlu&c9L80?zhP1Wkp_l6q-@`!8@^65E88i
+Vmr^A~p;x{Xf{Q7+gH^kOC*+ZzC1%mD1UuxslCP`0$S!iBhogp}l<)S5BJJa#S)QtMUas>^1Gxe%&|(
+)3+CVJzey;Qku)EKI(QOy}MleOc%>r`eu2zxGIFAU(Y@fCDJgMtMgAW~L5|xFc$~4lU`CT1>NVT(1Iy+m=*K_a8`bTi*>h+_lz|Lj
+U_g=v0W2`nwrDY6224Pp^3in5z;;efCyF^7Nb%Lmh2>qhXH#~XD?oW*~0;6u$^E^+G9+J0Qf
+-MjIF`P^J43YIVY8I`4Tz!7fsfI|3St(u}8+b^x8s0Lnc!5T8%#M
+@_<-{%pYN!)MQGDMt<3H%?h3ky~CB8G-E)>uHJNL~V0v-^m7o$i7B}K|MgNX5k5#74Hs5hYXpC@OsKPtc4+a(;N|o6(P()MI
+AyJ5LMHR+FIv
+(k>3pI3wWfA3z0$xW}pEFvAwB7jp1tbo;DLrk~PjF!?y#XP@^A1`%Zkc{uWT51U9zz!l44y=H+g7>ek7xTs2;7
+KCiJwimMrEL>@o#pUa}w?5`5Sa5eUE;St4Pp&J0Cw9><7e=@?s{uyet=2H7;SP)%a^pw_qfnqwzJI{no;~b~JcSHhi$;#
+3Y6l&BI1M$N&cEbG^5m+~SBrM5efyYT!R12=%*G{KpD_L(k*6+^hSlht7hFL8lilc=#{`;DbuxufV6%
+6xRQmAk$FZ}l&EP{{>qn8Cf*{jYx1JQNFE7flpYVuTIkDC5@2?Zx%RgQU7*x?ceZl^XEB*T2zh=nUHC
+MWf1TVy%5CA9wLo@PH2cU&CC=p%lNqDSQbNt=mvBq@(Bc90d5Irg@!#gM9m5=4tb*cQ`BK)>HI0FxZjXUH$B($y`z5^xMFkzkn=x6in
+V+L#3NBg}Bj>Gw0-Z`i=hQdVik!
+s;c0epw)q<6%Ns&0GA?SA5*j2+#
+kELwg{_7`)KTp>^Iv|tJR_jF&N0-DZriT_l|ogpggfZk?QOvo=DK5*=?o^9`nIo3nd@SejAhzM9(P=@
+u4Odvp;-h-lxn~vTnDAiiMfs(z9dBX!hEOR?^qV()*%3OI<*!mW2Gs%lX-tiux`aKU!`^V+-sEDtLpk%)w}(6H>^%a&kXWE(RA~4#~{@(0aEu@`M$z%;o
+p^mOIBg$-FPtXz6V3zSYN31vwm}GGIR7;tf2bt#q%=w<%fIzDG_p;6nM!6wE>h1y4ea|i9;sfu?3V>J
+V*iaT>XJSsd3Lb;%#kAUwwIF{W#SBz97bgjv{ZvQ47dTO-0vSE4%RH2I!7wBZZ15qM7&nMDE~ek+=Uk
+3(InavHgD~3`c)lAcGT`xz(nJw7CfL#)gspBAG(sh_Zq7hGL7P^)Z5h5XALPe9`+~p|qv%om2mVm0?M
+`1%`KNjB;je({V7Xjn>-Y%-Q7Eh-x4w7?KHgeg=|<$JGp;+m!((F8VeF+fF?l#=+j&9scZ{`tR;z2mJ
+TBoBcS-C@{3=PqM$soy6O#aPlu+kzEC8^(H@nPf}3jC-8>-4Bsh%m+EKv_BS~VO@Eq%yW|MP$`NU@1k
+kV=C-wAW6f~QW48D&h(^s0001RX>c!MVRL1
+0VRCb2axQRromy>g<2Dlho?k&YSV-D)blolPOK>T!w{I8V&|MVWe#zzpTB2=UBvBwKJMQ8BduR9}k&>
+LGH5OS*8V+ZM^YYBdtJUhCR52nt%|%N(DixDNG2M5o)#_p^MMH{W+v{Gkq9DBKgw%v?l&E{n3VVHV;q
+I%G@?U|WVK!V3Pvme6-PWxdZiHzkWF4H@6XfE-lYafVI#Wn_g+MVT9Iv*RqNnx!w=laXUl0!z1o51jLN3|_$-tZL?nnK&H$+
+|-h438IXi>i^hg1RSEuGVXA7)G3&?YZrHjqwvK~6&=m60FFn*~0+g_Nq(H{KtzCA~C#V5&fKLe#`eyz
+y^5t-oRO3*Lcv*O04^ki@dHipT0g7_uWr3!RlMPvN?L-ID*@+T?MGU-Mwd9
++SgjeFn0m^j@b4cAWQ(^3sAOKMzVJXvfG^g#r8&e5163GJ()E!=F!US4QUouGr}G_2nS5;@U&S2goWz
+kFY$n&SRa84oS&4e)b+`*LR;(|v_?z>Dsj$EI~
+hmZ=aHOI6Zp=p@qlJB)seNv;Kz=!=uxk6n*EI^Xl=b1_Nw)Yt}-pB9{3lTutCaDcN!87KiR54-1^x!>
+1QoRq@`-z
+)!dTup!_uxsxumiZpQ@s(?K2uE+4jNaDz3uIDt%C~y~zz>SB@=DGvr=sUCMOy}-2i}rZbaoqEA@8g+p
+2;ni7Po&(l^1;QeVPkvvUYa=pF|E8Qe6BIa&Jk)Nz)em!@N9@)u0(BGha2*dLX!Ox?Xj_;-52N1(&>nM2g(e;?j{}{Ah48Dyx7|c;IA9)jmWUfkN0xL2z0B!~;qboDseI{L{zVPsPXEe}7132McEva
+WE_@c4z@|EW(4ZeZ9nViZ;XH7&$Tv7HNAjQ?RQeuUH&LW4dszgmy`1BeN{r0&?C}kDA8;*+LQtwZOs`
+`SuLehk9ZlgQ6YPd&ov0gi3?Q3n1Uf@=x0E{j?srSZYuR6)?AYZc3MMCu7gdrJS`?NH)68f?0{-mJ$6ZtCF}IG@cfF@A
+EX4#Llx4*8-Sxj`?>lNdM-mF%CZOSUy-Ue^To&oaE7vijuW`Vmc;`Clj`*QpoA@f_w|5dV(|CXAsvh~
+i+BgTW2%+WSrPMc0%?NaHb`f*AUyO{g}fK|6cV@A;AN^~B@QDXqN^HpLmXH_^`_Y1;42yco?-nq?_5$
+$`znhdCMFvYaVJb}u7!9oyl`(56-0?%mWJQNfRj=Pp8vo92W;Quk!BHMRnU-O7=o~Ava7|0)=(k`;4y
+Jshg<9!EJT!9hCNZue`5tWlWVR5+~GD%-Tzs3CbTZe+x#5T`~y`lmiS2l{re`Iz^7T3O%FyfTk8E8I>
+3XigLS0Hus)K|vQ7vn@{O9NEfI}#P!4+sLvfyHEb-Yc*GkKGt?A2~4ob~Lw29^5>70YJ8+7D7TUzMy1
+`Rw1nhnVc$l?s&JJ_o&;w-!@Mc?YBxgmbgL^tD`&ooLtXE(*^v(28#Q*7a{o3+e&j#PIRlSfH4PN$hU
+(gW%WOYUd9mQhcj-g6tE(zEqGS=|Ni0`9F=(*og1$2_^<<=%+v;jsvNS
+2oyRl>zT^JBZ2n~qT3bjv@tE(l)wM^mRD}}RCyo9(j?;#LTfFb-Vxj6DhJ?>!5nX?>;78|!Qf#QiylJ
+qd~@Od4F9&xn@?dtnfm!S4k4eS@PfB3dFN@<36s)lcqy!cUioeyfPA7WwMv6h*4h}HpdW;3e;F8f;eM
+$GR30=1@Y5%h$MIT*|^|FuD`7?tJT__HZF&
+2Tvuy6sw>2f4**C027MAV${g@azT{y8*!*?0O)!}qmJ9}P8Fq27>#x9NyA#xP6REw7tFb&M33O!C89>{rt>?~1^BnRfaR5j4o*%+Y6jrHU6U2Qc)~US8D0DiP)h>@6aWAK2mtS)wnp4C2lLwx0
+02)o000pH003}la4%zRWN&bEX>V?GE^v9x8~tw^xAk}Z6~qO@NriN7Hx$E+dYEgwNw73AVy7#X1_$ay
+Nt{o2*W;ZXZP@?5_d(u~Cs}fm4rqYZ68ZS}zWE;c6h+adA?1=8(3B=Y05&(l`d8=FPj
+e0gKpMBX87eSmQ|&SMJzH|z<+hU6@ax-qq%Axlqy6eYokmeDqZpC*oc)XR4wxkVe_CfAi8K&T_6Ed;?
+JzhHeVERGpeLP@)>g?ZcCs_WUx}3MimW!=eb86d|8%xUOoWXAc=)CdQ~K&Q3P^a*5yW|>9TFwTBWJbo
+2slEA?K#d+eW4QeZ+9fw#b^Y%#8yB20&gp&%%bpkehr%1UyHrOvTN+>+82yH#g~vt5?@kJ2$eB53(*m
+s>W^uqDgeIP#+zp@kqeG>+SW+sraF+H?p}Xi>5B~saS5BG;8v74HC*#x&c09o~n;o-Y!%>U6kou7qZ?
+?36(e88kIh*mC7gm=6RV(7M>;G;ynHS-RsL&SLyZn?Te{+cXRbNy*R&kag|=ad^;7U&eEy|^ZGa%jlj
+&rM&7BEHIR~Bj#n%Pl2AQ4B{EO}`1h>>=@+16UuL45|4U^J0nlP0W-|erZ#Q804e5NLvxdztF62hVWG
+AuJd@SzN*3f$1mDU%%o~~&$pu{IxocZL88OM}qdC&WOn6Nm@+O7nnbpVcv56tTjdmp3YmH
+FH(*kj;^VNv0u+#mxb!B;8GhohhRm>4N_mJdS*ZnRZ?vsj(GkIaZkVu_%AL#1+~bHn>-a5{8($O-9Dz
+2qOBGfFZ^`@QM}q)K(kXPOw(3XuEF+zW7*~nf(%q^lE9kuGTZ>;Ff#6GA(-xT!MvelSE@lv8yAy)ku?
+yQl1)>O0~}LPOlc3cC0vVm-$$$z3l&X8#QO0^oc`R<2giggw&RTiA7)~vjCL~lGHbGi+nJIBanUfr59
+4ka|HW)5cmknGfIW(G>8xhaDpVRAU2bAFBd%vozHx}`1OQkH9RvuK@{vAnWlupv8h|tEg=s$`$6VLc|`Tvir%&bo$;FDy2CCIJ9yNpC~MTBD(-b%78^L
+V#l5UGMgh)dT6Es1#|LJz)T_3pgDK&O$#ev)*Xq|+1ATxA4?jS8i2r8-DJzS7iu_B2_OuxZ(2kHN*qaR_1^m}8EiqrECw<8j#K
+I(>i1r(#2!$kbbL=a>^f7UapS#DL`OYrVq2F&XxNH=OIse{@Z`wz_|dvbQV>zSw|in
+(KQU9QlxN}#MwG}BCH3mV!9xIgp)4QO-eD(9QLPw)Vcwu#kh++*(DBz0!On^8D?YWPsox3nemc9&U>i
+%t-J`19$kPI4s&%0b_R8Txufik7C73Aaz@_+f*sc1ipr`kKW+~j>5O0kD*TaVTPY-<9_$?ZZW7&8D$~
+m?*aYtg@fvl7$G3QoLHHD_t%t?mKG1vuBlkE6WkC{A^lfUb4w?vsNrDq*m;D
+GFJX;pC^()BAmhL>wz;)U^_i(YyL#qMU`T!ON(Jv<@>r!XR8|D
+FX_C7Xf{2KrFfd8AvAAHoz^m}h?a6i;fbdIt^1N;1?HA|jmj^!Snp?vew#j%+@=q6iF#aK@;+k^~d{4
+miK{+&-CorlsA9e$Ll`k1>TNMHlrz+(y@T$zM10jUq7aCb82bm!G5ad9r@E#B!6l^GPC1yoK?pp_u^k
+lbT9LOE8CPo?+f=9OT~0x|2Q&VYDG28Ds70xEh;TPTu9UGT-B3W9M}>w9GPPHibk!+8%A9!J+#KMJlU
+NfdN5mbP_%SP+_nGeOS)n!i@tO7k~QG#$oa=zjP1)j&3Q2bUPVsd9x2{0RNMgnSBC;Hh-IZPs9`*I;I
+HrNm3R%-f089HXuCH?>SVLXNmUc{MEXx!dvU0HN9Ea6Nkm9dw^JGs{=F3G!@^>B@3Tn<~C>F{D8*5=BBeg6=9BEVvy>V4OqQtxMD2>Y!Y
+h3B1yCtBzEhEEK%_8gT9l#mD!#7e$8{&vVDR;ddsy@%7m5*mSCg{ct43iQ|g5&@+VIQM-@IJVooBhF`
+q7xqW^9zzlq))Ywf$)YFGMN5C3mNT_{!Q{NUYbC4V$y{|
+WvC+C@&S5;2fJ+6FXJOQVC6B~pB)0P3Q|Wy&-H{GGYwF6zXPASi5Khq!t-d3Q3rR_xMnBG@bP_dnt^B
+iFwclzvLOq4T}EbC8V%Urn39CVd!C7JF*e#KRsT|X1A9|jgS}Pm@@IQ7aa1IVMdILh^JaI$@x{Wx1n6
+W>;x+ChLRW71#*OKX+n8QRplD4%AoKvr4F}=V6>t(Q@KUs_2tJyyc)R4w;&n-w6hB?u2uispo$H%AJC
+be#1dGd*ZJjCq^9)k4;df5W!po96O9?V8tok7*!oGvR&&)5oe9yhc;thH)E6XS5hfV1WDE2S2Fc^A|I
+~Xye@9<5AWb@Y(*f-j{5Ytb79CV@B@L~vJHP*YpgIykTH2V4HpHI$B>(~crnURtM6@it{}k^A+YUjNnE
+4A;6*<~m7plQoo%B}|9;Lu1e}_to(ft$=(M35G8D{Nw+YWDd-uX62Rn=UkZN~x8zghGt;Z
+6PCIPpl^$DiY**Tm7>voj`0IytEb%>ss98i6e@b4d8S4ABjp?byJS@A4q`sP`nGLC4q_&Xnfka6#ToeD|fi8#+PRY4F}8fI&83VfdKyJuL~E+ITS
+2joq~X@QM3xC|eBw)|M4nkgQCC3Zd5JNP#Ktq*s~P%l|lZZ=SoxsFBbg-pcjlG-An95v-e?cnMvRxDP
+VT4#5BW|f-?Z$MEv8@YwhqH`R#M+%3n-8+yz`GHTA_>`88?!Lo!oTG+^z`v>K1-|jy8z1Kszgy!QkoW
+&LzH4V)2!a{V5Sw)eAQEhE-uj)d`z&r?VMA=Y7MFo66=rfrDk*})Tren|y48b{XwXI)KW#
+IP#L?;%qk$75vhlv=sENVOJgZK5N}R%(m#p~y?QI8F^dn_6I|DSSB%@I3lV^H`Syq
+;|*N>W55LYlyCAC#|VZs5U+Eu|e4SC7LAS9U$vM`VgVtkxQNb9i)%S0~#_w?-J
+XxrL(!ETNdfC+8-|jsQOn1tP!UyqguHKE%)01nk7pOTWOEzIp{bI)EuJuLM_|c)f3AhRR=1j{7NldxO
+HY=RZkpaq|KE)(#l7X7QwzSRPa`9W&O+P#{~*|G^drWQroH&gIArHIvV&)YLs`kJ2y(fN6VUw+ZHyZUwea!h7GU
+0O`^im9TvG9e|zu9}&$H9Z!aNK`A3osvE#&0uw77QmId$#BW_&-of0|XQR000O8@1eFv0<+G<(;@%>z
+LMO4Vi8%9MW}9O&o
+sWnsR`byk!~*A}HRk5-vlmBp3$xs>fDQ}*pIs;e++C|;0&F@B^7a}qTBCM
+y-dT_Dvq#U@E_WR=%VqKdm@UEfHRbWI5)FavI~MoEz=!H-k={qXLM{Cg)=>j2PC?1BIk2OXY3QCG@9I
+IC2fl|VyyNHdy=Z()_bE7Uq`=c~T!y3S>3yS?7NKI`AnBAZvS+LT2r@7kGY)@>u@OyK%tZK{f2;YVIT
+gKP^^ZC5iQ-^k}Gn_qXEG%o<3OkGpsRNfV;RWs358+e`K67B>rhe@TXyV^AMTq@}F^v$zx&whBGKL6?
+M^OxsmuU?){g9#)E5ZDCR!^~yAl5;(HeOt*U?4jAzG%u=LR&5GT!-id9+I$Uw$~wzmH}$ds%)ZKCis8
+faO;LHnmGwOH*6p?d8mF)%68>3d&9!WZAbppcxor$z2Rs$95Ksu1Y{+@$b&}=Trj?C_`BkRmQ+@_4^Y
+-m){bL`9u3ee#PvJX);J)y!nB${8y?q9JBh?1TF|t=|2h}#+9L1LH^b7_BhL@Ep+TuojUCi4({gKJCW
+#5~3*yqC@^9=c#A)x7EgYJzb;{}xLIWbQ`NgRfeF8$XK|WV*&5W_6q@C7Avy)y3D&-9<0*^y$z(
+$RzLCIDmB<8e7w-H4~2~H$Za~
++{MY~m$oMeF!?*-d=$YZ$t3xuE~?lY>jHQ-zMG;t7I&bmhJ*K*ejI84BV5%-;#rLyS}Fbt(p3Eumiw%
+!s{yzDFX3f&0Ii&91-rXx)oszP;t9yQ?rt)5*d9!o1{XUAB@}~90|nu)R++7@@=V;Fh`Wm?m%gF%Yq5
+#-WF1)nUyFQlIqMOnVN)8bxaxQVJ3DwfwO>KA&HpiM0D_B*kH*y;skLixpd2>wcLr04;LeHs_{HtQ4T
+4UzjQoZ=@z?toD9@VPqMA%aR^>h7>rCT)aeNtyEN75jdcQgsKLz4@3|22Qdt}J)jHmuEiIOcSq^v93c
+*0Ep{QD2np4EpShQPH*`v~Z|hd+P?GaLbCw<@Z^2p5z2rt^W=mJFm}g*tgp&8<$nyukTget0~1u1S0O
+LA*zg@xvrx_agQNKE?m_%!Vyb@qbC)tv4#Bw?awqx3acw)M-4K;kunnrtlanrSw{E)hT*?nq|>6n3v$
+Litm72#n&~sx0SV9V`I<4{8`)G(QRdLY^)S(mq%Scv#vghy@bgIgRkmcxRI&wIQ{V=vJ8&|qEjaa8GS30typibZ6yWdLp1
+S?19y8(JNka}{Jo$GZ@RRAq(UVJeuyZ{aAR+1neKlTfTiZ7-4r4>>X^c!0jX<0z<8*QIsXD62)5xz&i97a>X9{zj*sSF%2DOTByBxTyJ3Z-XnA)pe(WurRJG@0@Axqk}%opH3_gzmj0=o?yDpf)q{$tTyhS+i7A*A
+V&<=2MlhoibR9>lZ~Pl9F8lT(`mA7{k2sxbly6a`#ZR`#x!s)Zsm&cI-aGQ%VlDBLSeVQ!%~eH~zk5+eg~
+aeEK<+qb8pkN*|brUO~}y#aKn!0Hf(BG{2VzIH3?XltVXjVG_o?i3Rb-+p)Q#1RP(F9-t;4cj=<$H{_
+VcYCwAf0Ls%U#SiT&>;Qd=oBgSsh}=)GZsO3u*&M%+PN*0-YW8l0lX$&T)6WT&AhnnNWMXY;{A>p}
+g8O?GSSBn+fj?|=t*n&)QN-IR57perSQhg?E@7?8ihC>9MG;Yjc-#hx&nnWd&2n2S}8($0}6gKh~(R$
+!oQl5IBJTy2I@%e3xA*HFo@1k)Vt`R!mzpyuOOLoKG==yCJo_|j{S&`t=N>FL9Ba5fDR4e*2jj>!zul
+GS-Q_g>Lklk!Ck7Th=II#3upu~pZeB=TDu4;O8QVJMlcPZ?%gtR={C{R<4`VzJdf=CaYx{po@5yDOxk
+SDw|_uC(lNqJ6O*HV|=|TTDncp87k&mG!6Mv*Tm`HJfb?uZ1mL@6Yxd5C^c_m
+8>RM6vozQD$`%Q4UL7+foGU567`$t=f+{pncXJAOal^T{(t;pi=l1+J@$(J;|sTfdOivRy%YJ5@^b3_
+HBYRD1NDrWmw0#Pgee2&_0~EozV`mImOhHCK~x|DnJtYbA~JLUBwFMhquc{7@U(T;48F%I?wgY$0t_K
+DOV2`0!Ifp?R=3(P_X2;&xSlH3;^m0tiNP${Sh18_D)b3$hgapuA{6(t_|5>ugKl&`7yLa|)AK1)4mt
+>X`dM^QR9m?{YamK^Z-2|EX>Rg2tN@8=KR_!U%hNiWf6uI2qunj?9IOO`LuX$UeZBXh5Q_VPe?V4lE!
+IX4i6Ywe78>dDk?k(dC_-cjOmiAY7a+L;-}Vr6Sr;O3(p?Mru)(oVX)kpjQw~k5&ijrNoAd0<~bJaVL
+-jE^*-Ta0L%GMAAbJA6FKGyimAC4lL}&L_mdK)=RJgfv`xRen@SYhKWa7&%{n*>PsQcY$O_3JluJ2y@
+iPmRfK$0fGi!5C!FhK2#qG9mxt3G+RNhEtw88rNg#zLL7s@$Y>|z?au!N9>q03?aw0&QH-`i=2SH+i5
+kUS@kdvpn98~bO?r=M?^Ma2eC62xoi!xg#lcALI3GKfh@h@BA?VfzFBVH0u)n@uMIZoy69tXq~8wB_Y
+H!_9>?%Tt}p1^N;T}3T>{#lR8jQO!&7qzOpGAD5YOMFysJgy7y84VGzKgcIPC?5ho9b
+Lr;DxGy<%>lC$@IC+6pkw^-V9zV)-Yz#)xYSXtN&m0-HXmO9RHH*uBZ0@OCKz~wkXje2R$mC>Y3R2?z
+(>HzNPHYX-+}6e1^`MarD^7bLfRF66y$8qZbf8d;aEa`tsGw=TXooOR=4@T1X@Ky4ajcQpvHciqQ`Ya
+@yq_=&uJGqQFHBIG_N4UnQG*gAq`A3Lf@x&o0c_enWaT(Q)&h#WiKqRjt|(w*M0PUbvBeX6Jt9A7vHm
+$S;}%6rp>Hbs)P!Ivm<%AMJu7dPmfr(q(Q1%8n5*288>7E5mf`w=ttBM&BQe0^q0P87$wtZzMRcG>k5
+LO5VVdYy~CaaJ#WrL#8fA(>jGi&tVJIYw-UZU@Le_sR@dL>!nRbIKUe!4z`y`6t#7mm7?m_S8N`d?qn
+urXwU*lx-x6*W`(UCz(VoqZ%l%Es$tDSJMXFZzbc?~!fztdcEHL`xB_EI)pk$2r>7hH|c$^vO9jolwa{<24js+@pll`a*Rad@i>3~
+BbgO&&Xr^0lMyfMOpn7Ho+kQT+AxA34s>0KDFC&{UcK`F8{sB;|pp9JXC51`E1Pyn)Fm2Zkgjc&T#{j
+9UQRPW4whq?_Mi((L=8({2e+Y!hfv%CWNXSB52(fD?X9-f=^#u&Lc>%$WcBv30{QLK#w(2JCHyOGm=X
+Ce<3EhmWJC)P5aJbo;`fAa!dV&y=)mAJOl~^5!9{PbQ4+uwTUIsgA1qQQY2X5b;hbf!
+1_T>3Q}z>%bPY*>cw*3_T&4?oy0baA4l(hQ;$&P=tPQ6QibIWp(+vPz5MrP15(>BmXfjtvW>L`$)Wo2
+b0h(Lk#ab|HTy>XAxh7f8`1iEM-n2+X6<8=ENwI)@?~gw`wv{f}XS$O@Xntt~we_`@?W)eTwYbm4cuJ
+Za+%=xH(8WR4%B!jJHLxtSZROSy>Gmdnt$%tX|GH2cJYnFci^HUs<6#U&!t8=oYmh**i(l=aUHa
+dlMfT~%O)A~Dfra@`2Sq0iQ21+XrL?#XfTrSN1h|EZv88!Yn^qb723Ew_|F1j=<-26-YPJzms@-1IWi
+`4A8fsRBX1mLLv*1+d&ZrtFYLms&YtXKIV-4Sb#~o4VV?Po@d59d1fj`^2U#Rp6E~BBj``*uZ?jPygD
+j?u(H4(KPTSnP7thFhlhfy@ThOQ~
+;h-X9f6Z#DCj|xL;aP{k^MOIgST|bk-T>w-YURX|O1<9(EHC$ib;dY_i9ocqG0#`{}#qC!oracx0~!I
+u$=UDXO;*#Xar5aN8urhG<-;Y-h(olo|P=xJzFD?em?PX_H6gdY=T7f?m_%BzAXpiyFL;4~5hz1BdfK
+vtr@HG{W&zEP(H_+|+C9LvydjGqMlbh0!yLW>I1SFfanE-;gH)a~g-34vztRPiFpR^71V6d4dNAz2u^
+7AxwR}0a$Sp{a1_<<2O>izaji7ZZ&9fHa!^GD`EG?pTrm0(XW@&pMSI6SPk(ZnwWC9R=pT?(1Q7k%T1
+*>n0znm`Wl2uagE9agp8vGa8Fg_JW^9Ho`O^5gNXRRid<|LNRH!6Px2al%RK<}1xrql97GEIVh-{w;5
+n93-po}(XYHtyG6!g!q5)(VKS>H2?ndG%(q2zeRSxe?uW6=~O#W7P6vd;eHkt@SN
+8=Ek?Aq#Q(7Z^y!)A)5Q++W*YRXM!gvksaq$9;%OvJw5Be(bxR_f(5-5)$yEP|mYYNboph{?@5hBYj<
+h883?$ZR06t}CS$zJzpYCO$J+aa~WUC7rjdB-eO(oFiK-S$C}u4M
+%krV`r#COikF*c+8_0=<~@wgqVJ@_eWVjA-RfYNWmlBY9X=S#<3G+)d@3K{BrW+xoVg?@j!C)P$cDxC
+$!wR?we-=V(GywD5!WkQ1?x>@@T1T*RceL$9>gBJ8QAppi^u=|EbRtMXhjwKJaz21ZVQM(xbHxtI>)U
+(337(78P1v>!EMDT#})Ok)Fr(x@a!CMp7NVq*9ctrGq%!n{alpp%1;|9@*km@?=MUfd+%Nbww__J^aO
+-7V1QOfDkuf0BL=%TBeU)6OP@0pt;;LQQW5hw;Dv*2Sg&Croc0Px6soa^Jk^lK&@xOfc4eVOFNQtApE
+EUeWh?vqO1GCA-89d?|$O?ztUQi6F?U|_inya+rRF1VkdA#}yQJ}N5?a}gh^DFr|&Bb09;a=lKDNSMrW=0UzoQr#%uMMZ}
+PBY8T;rN)3s`l-P-(L!<}N$2Yvb%5p@G1|L!>;HjBIHXg^33DgQ5AC|0K^2CSuU5@r_PH$RbFFU)oX8
+c1|c2Xx2tw%f^EYN7d%;ft@2kQd%4CX%>@E@E8w(64w$z!=d?q1--8K?6+=1^^4To)0SQ{v%(vFb3s~EIR=@*f(ISfV3clG$Vr1U%?u<9
+9z`ghUtk_L<%n#LAB^#bEw?rLgPv2lmA6zqH3nJjk7YTB?Ru{J$nvZQbKIcoeh$QsHc`EKQnD*8scsX
+d2)1$T7K~m*n$$7?lV2@h0_W0UJ+zBAy%{A5V9Kuyz_jz+LG=c~C#sg&
+)W8z`kM=nJp;&+l>vYO-dqz_BLtM>7E_&NSuQTD)10Rrq6tsM6K5jU$9Urs=oM^3)F{9PmEy4AHT;S;?7g?U8-(!lvpI5L#7)@pwqGO>uK;+~VQ>GPb1A3}r?F`EKMX~I_l^QB7?tdiCG3v&B6r&HMEap
+xfGT)Fw##FWgjIh)yOfrcRuL!S{=*(M4S?QkS2;p$A7FCUq>rJ~I(B&VW$?m~R+{{~N>37Xy4+u;x+g
+=%E^1~^dh~=-vW+x0@_y^9D^4fZ$_Lw2oZ{X-7aeXrgq1R^DCfh<(ij<`R)g9lv2ZTu_=#2~GE;A5X_
+*8M3$Eg#&yUrOqUx5vJ?6-dLGqix
+iS#pSKVA^7&Wcait(}oG$bnTeYQExuwh`OaTwR82ti9NYPQ^OczQr#1>8|RHozxz=G;N5=I=ee{hY3bW(9!7mc90Trry
+0zOC-gh;)c3f>&fvSvP7i_aDK)DX%*Vh-{Q+nnIPG$I)h*-5ZAg|4r!XVq!P`ikHRwhhyxx!`-*YDDA
+1{dKdsAA-4Qq|>^E~n6x_=2>f9JM%u)}v>T=q+s1*NK8tEW3wE
+Q!#_t>6*rTD`Gr==hfKi3bfPkGQ}X;^VXOfhx#m(N<>^tv3v+-6W>k?oK<`^rRy0*Bo6&$bgp(BK?r(
+A1?G-!@qbs5pZxBTJQdI^%q?i*iVHxH!Qm0ww(Gd?vw>B1=qsZnj<7$%
+oUH7(0NsK`{{r#l^OT05Zl_OxunB?jreYfq}(eYMnfyTw7|8y@wMq8*U>p(xCx
+f$wNM&^-H2W?Ojnh5=tqKLS?_9TiJsN~*T&4HA2Z)hryepL)Qm%GAV}g|}d>n{vX0HnE_6#qcDg;Tz$(b`Cr618qN2Otf
+VpEQ70L*W)G2uwZ!|QaoHZHhAV+C&3p0qjG-|uX=PJ1RVW%)8`nbyN1+AR&kNw%wZWl-trkr$NmddyPC3TXoS=?ThoI26
++(TeN;j_L>%fn(16lk|q;UgF)Nyp=qeHK^VJLHZ0;pL@eA?FY<8h}WVwE=N1r9H0$)L3;_STzdd*kq4^UXQ`_2l_w$?8%@0XvN08
+s%fB>^k6+SRgy{uOaI|
+PjrJ#mByJi*9qrs%br+9z<(Gfm4LjBus)Jr$`#(MlsSJ9<7W&t$M@NIrN0;erO>P%eO|7i*LirwI^T*
+N&}AY*RxQ%7=8oLIFv-Vf%c)vd5+EXXqKB}G5c-Qg*CjHg09?gU*j9nPFhP?|e|Jk)i$G;Mn1NVtLU4
+;uJVRQu*QVYYkSL+n4%Up;t>2V#*IWACpZB=&hPR9~~@3o_*$x5Ww&+wPt4?+F(-MTSlL6G}`sH^*WB
+48ZVgJTC2BN)dDG%+ayPtd)3jYLJiFfqoLebz!(i<8d^q#NBy~rNt;XqnFxRatDg8$P8QRaELttvFmI
+ao@JYv!|V29yY#Bgw{)|?Rjq~;FzClFS_4@9MSJ1>0QX)M+&Z^Z3<@Ipd)3}Y?v-iLiHPVzHdvtOgYP
+=@5m=8t31~xo7L9JL_NG4w$L;=Tluq@(_Fll@HFE}&v2!PGJ3>>_AF$Tn0#JNhpuZ#70FRwfZU=Qz_A
+UuLQM&t{zP(kH_?cjTeY*Wn>Ga`Jr&LGFrC^N2qUKxy$O)@GQB5M2?}I-qS{R~0N6t+%=IdRj4jqTxT
+2*J-fg?BYhiB*X%OM-u3+@OrtRLk+O@yiC(4ZynQQ8SO$kLU+S~Gaj0@DW7RgK=3AeRCSuK<^$z5Vfh
+U}MkvIF1e-1ULtMgPG!aXFCkxNB7T|F^vR%M2CB2&Uffx9e96yxY$(%j-Pw#>5V!i)Gb8H2<#{dcYr=
+9pks$X6Ve^RbqDu_qVdxUX6JSn=g3(YkoLFapuEsx>OJ*Vb!_;sN;X_gr~GxtAG#VIJa~A7)NV182IW
+mfx2Pk}*%*39k$4*i(~t}&>DpaCQ*TaKJ&N4xW<4D;ax~DRQ4iOroh^rZI!8I`l%N!JX>PmqYvKl1GB
+Xf;N}I!Y1c_43ef5y74{+m%;k)?H*S!dK)`(bW_I1gEFJC8z||EnUT1W*2A*DBiyTAR9GVnC0fXWANmzE;d5OxyG1$SyD&^`UF
+70)a61;EWyRJ0j=0=-CX<9z}T|yCxM*9Kd@sKbMhh3E4{rtegb3sq&2>KxK(=3xt@Eh(LL7Z&VwUtcH
+-9{mh0B*lLoOh``7>j9Xd*3lITW7+23fYz$V8c1Z`G)Yr<&twaEOvh_`jrczHC+bsAOw8G|dBApBU`K
+c;2y%$*+l&~@5HPEEi7h*O|WK~wz8J!|yA=F38R9}GVp>D*J)>d<%Z>gL_Mks@+?@D%U+2J6mc9WU|ZLGke
+Y6fZt+gAx0>Dl~ciaB8m(2vISCB0CNHcQM>6A0CPT*~Y;+VG2PRGi_$OP5XcuovyN7vFJ#jt#a$goT&
+Z7jvXDaFtMl0N0Rma`OW*XL)H}4H)?w>8Xf;>^4$Nxne_X2z-&k)#YGr6m6n%3%&FenX_l)L2~#R|0usyX-fT+
+Vm#BC%fqZ4Ktkh8AOeU%N|mE*zYCMOYY&!A;kMC!|)Tmwmftc@(`cx6jxcU)4{`AYY0RmZC5;GHBwyW
+QO>KMF`r?Lc3=LmW0B0qJwX{f8;z}(5dZ)HaA|NaWq4y
+{aCB*JZgVbhd8L$1bJH*ohVT9rJHE6Wk{`g0&=xqrWI6*JGaG9i?;uNNR|@3cBl&|U(1y<7o8^6JF*LBs3?_sxVV~9e#9dJK*34nX`HU`>F?^RVb7FA)(!@Y=?+u
+!t8uPaDTVr@_f1|EQxZ`oi`esCJlTFmDYVqu*L@5qs$Jp;f*VRry7#3l5zoSWCY4}1(;N2T5gyuAxlX;@crF-ZIdanWEunK-AJb2>=ynicMebvaqMR4uoXJ8kGh
+F$%^k~&t!^}TX;vpA*II5u9^F2p(uxT+!ON+1dym?EWbH9D-L*w2w|km>my~cs8%e=ZRhnkP=%d!MXNM@x{!jYO&>
+6-WE+Z-3`A1AqVs%JwGjQ|?_VlfVEN3#R|Wq$!iv``71_Brg`~qpAmk!O^l
+RH_2?a+%>yO&1Oly*_Kt4WLI^$-Zg3_e;*y`_v`ZdIxnuxpY!WnAk544TFslhENTOy$Tn)x?6zxVO6Q
+HLnzCHi=0&}?KTG?ovgJ4VMm77bvY)nPoqybB&C0yE$*O|7Qy)n$w)r-leNe@stWqkOZpsg;PIuLMon
+M)5ZL_LYNmeKFdd7d>y?Uj7*{Qm50O&VP7v|-LI%#U;+ibC@R9&|PXkOM9PotxRp7UZkN#@ynr53YoR
+c;AW`$fx)a`EHq*FU^@`}|wy
+?KtR3x=`Cn%`;4&>D3#B(J#)6*ZKDMKMR_}YML1AK#8h~W=3z@g6iX3Z9TG(;eMOfO_g8m@aa|AyeN0
+YVv_uk@c5IeYI^x}U8!ubKQHPgBhsRe=fwva@Z#r{QtKD_S}{f#*pEfM+XA?fuahHZgp2Z~SeMztwEx
+}Hi`mlYBuYyhz2KyED7HB{WXzfUGA#60{Hx6m$IyyR<&1mX~r<^74hA%*`&9ky-s&YM?B*W#Vna!K^Y?T#@wVG{+0cAkme7##J6nM>@
+R4+hQ)$o$wJxJbW?^Tg-cb={2Up1_gHIY(ME|Xf#iG?L)mD9-TWOSj_DPgGDo<4qjl{e{n|M>WqYJc&
+6o{f(#okC8<05m;c$2*qKjMB
+mLj8U)nUngOi_rf!TYtr;j7I>513U#D_F?hgcD((?fKIQ7PK}ie&JIIIjNsljN_*@}Cp>&-m*>
+x@3|bStubw4)$tdt9kufW%a7JfA~1ENbu^d+lecim8!axr5(A2LuRCBr3u?a(pcbE{vtIWPO*eyFSE4P*vY71{c1G@j^JyMh=;zRcAEFZ(KDgSC}kWb0Z@oG&ERk?c=sQMLZqh0|2K
+8qx>$t@;d`4D)^x9_ks%g$hF5d@p0^1wsvg$LZ?A7>O8pv*(u%5fR#+5xxnRnNI*|OGT$d#n~nRbecR
+(JTW2>ilHr^H^(&ju@)^ySMd5t?bp!D?1jiFA)Y4Lgp$z
+joUmEOyV$siFJ!LtGTyIIuVXSdpj6pbKZH`!V#tja
+#%oNdCCX*9(Vw)8u?fBeZ}MT3l5_sL7jE@(-eI($cbha-|#?8SmWd1j38xp7{rbX~{^a+NfkfU7j~(w
+NI0alIU-5l^GYw4*(muB?Qth^)YAhc1Oqp?ydf_Fak>IR5c8OUFep5WVAexfVF@4g+g{Cc=M
+)JK|LWU|BA$XLcSDhR-|pOGMd#)v__gzTDdGGFKC@_)T+D*gjV@z+o@GE3j6GHA1V~Qpn3Gz(TIUe1e
+Uj|zAcj*C489#*KDbnu!X;8S8Fv+3=bz_10z-oUOn|UevkakLU-G@5sZp4Q&<7=^2>W&`gxVpj)@vIB
+b18@_tZNm{wpdKI%~EgboW5t+FeWc&LfyxyV_O{#bfF9Q?vYd%4va{ZaeMr+q$%$%F(ZyukK5~`Ar7JmWNIqxgkzYd~uVvW-vx4?^=-WcDze`_xA1U3%_xh)|t_ijrMCE(MZvGQAWL
+wXaS!vXCqH`0B`YMC=wQE1-Aga#e6|qfZ|#-nEaY(gK$Os)nZVuZkkFnqwR!;X3cTt!c3`Y5BZhDQKln`V<30^i!}&Bo|uZx=};IBg+g>65cCoC<|9at!)(gmkaQmr+C53j
+dz*1FE41)fcwP+lT;)>KYjBGGez^iU1ua!Qo}SyT8VIA5pr@u88t;fT7f}pWZE`)+F5Z%Ua%JQbiN{H
+Fbek8Nw_6PHgz__fFxY|H;ml;qhD;0dTBi;qEt}6wK1$mY+!3tsu@#Ps5Q@En{UCfX-{3|*E_5s7O0G
+wM6)|B=tiVk3yARc_~POv7}t#=$`^sZUMHJ!v0Fn}PhQjJA!0a?eE^nSTWP#d7eRQgNcMQ&YJ}Nh(V<
+hV9fouBfd2R5{J+0{eoDQj?$xVuw_bqWNH)D*msiXTA)V4!7P_Sb(b0PEK_L;0HXb5&jWNjVHMAXILn
+}6d(t4}r;10mB@?dn<-G^+wQ?brspPgWF=aJ;GwG=)hX{smkncOivLF>
+D!i}yyPBhu-N}rSuNtAadI%52AL6OxQda~pc3B)izSJ8#uCld(m_}Ijk!wvQa-&zW!)5ejTWA3E%W>*
+Z39g{7h6heX{gHzUKYJTM)uc)cFrvgZAJ=~XzIS!ZGT^gC(o>~ydZjEQ*%8?MsPonf>e(gO9QYKk_PZ
+wOgk1zM)Z_dpe>MQos1a51Qo{7X8F6X3u%SGm-4r}hKUkinfu}QHT9_wl@i*EbQ&Lg<4oOdt-uwdiq#
+?f6L3R-KXW}lQYci!tQRu9-hou{dqky)+0vFgZ#1gkJ-Gx|9Kt>K7>=1?mos9xMoXgKep8T^x>XoyUL
+8-7#2E6Toy^lCzxy>4%9?RNgAH`QP9LZ|8k`Ag=wD9*#i!HZ_$b7X-wNDQEtu|z!V2*XM0XhexbRaPQ
+GO-k+3OBbcfnvHuW7Z!sKY7SE~|JEr)dM0wt{s_cfGYRg|w${Fo#qX^m|Kp%+pe~Y4Xn{2?XNJ1juo`
+RgyoJhv&yU2Z_~!YVin&hk72Y#XHb`rw5^xSvU@tbB*A}axI+lz1r(-cOEfmyA0~Zt`O_sPTLW$Rl&w
+1k-q>9mbO+Rqwyl3IVj$!)yA
+QQ&EKmVt;b>zM_5z~g{A3$kRmSPk5jRj-_?i~L%H3nC+!P
+t+l%0`_gtj4k+Smy$oCO~bj@B=atNtF4#W&1BfV^ore^CPc;nl0w7tYF>WHG+smz9g)(4HMdw2w?#Tm
+OU9`840--OE)atu)A4|wCFid$-adW#^7*&3pPs)#
+?1sNcXxk&m$Mzks$Sx#WoHL#_zxD%GNyYvg3$E}*FphUPler#6+F0sZo&6IOedAJyT8ttrLi!Pga>O-
+THat<*{d)oe&g<;FL|JRds7K&P-9YL)7OT(<$(qBr18;X&bo)e?n6~s|64
+8%u7$7}qoLXzHmM3Cl7^whaC?V#=KHh#$p%4n_5-d2td}8m>3Iz6F4I;(7JC9cl%A7f&mH3tslm
+Ny~=oEn^jfru2-nwz*=S1VqPxPq9+b$0=m0Vr0Zoho99R^&oLrWsmsFIg&w#zh_VoTdak#`xXV+IEWK
+%CvuF@q^eAR^9f!R5*-DY_OslI_dOnqnMiT$FszcT)u%LN-apv~}@33k+FR9?*%ZSkiPK~_SDPOp6s(
+rWUgXCwGz|M%?E=s}FLSYt?1@OZ=H!<=`S>va8XCk0znQ}#O<&p{uHLtX`nv?Wn+Np(jO1P0sU}c(bX
+ZmM+ztF=!zKnh)MD`n~+#m@9g`?_HD0-|6j+3(Lt#2zC@9#YfN^C#308&BmXs~ORzaMmKbl()Jxhbpn
+A*%H2_?Ufy>BpFZm#|8Rp8m0gHSae>i9Jkyf^^Nk^Bz)lEVoxBJ8PNcCK@GBSj{Vq8X7?vfhE@Dxhzx
+=yMvB-<|56&NF4z#OJy?(x7z`b)I%9Tuj|p1t0uysBc6@EEm95_ZiVm
+KaEC(GGrra&Vx(j=hkku$_G^_O_g6-V`+W
+-%q+J!O`0P=TFmfNg#R?9n{AcDtzf7IWnMsxaYnM3lk%B9`BvC15e@r`RTW@{_U>vLYTIY-bNaxgrK{Sxd*c0tBQ;M<6~miK}90w`O8@dvSrXfz*cw
+^;&Tc#3RV@GmJK;705Zp!@vqO`)GlaF0_g`;${O=F>kl9r0deeaa6X@fps@fp7!OAqj74eF7wc|J>YH
+_w4v|f_2V#`{Y|#lQ9Z-yRF8McqkLEoL#uZv>D}#P*M8%8C0P_k(0l96a%|jQNHPZx_&UrRh9?W7HEb
+_&s(=w3_{rLiFk7+KW`-=npJKHJM(s+R-3*#U%yAp`)ta$6C`sgRfk9J6y5{}Pu1
+h?F7qpUuy~Bc=Jgj8@VY?N)!8_W(YJGrjx4o{iMV}{pu!lM=lY+fe%t>yrq>lI!mdCSCBk2M5xaPI>_
+^&AUceagr#5f7&Hx{Ox+{Hf{nKhod?(&dXp>7z86Z?wA(+Srx^`BCMPBYl
+9vQ+~_-)3&l35gBWIB@3d(U4E}}#8^zbmaZ)UBIst`Va5ne3l^9$$5fwx{1O{N(rA9@ai2s@F2m&
+EKF_hQE
+=pef(iJCYc@ECNkutqYgh81&JsK^1ix7}t%amAHq4-6qgu`e>-_rzJfkscVog_c>lf9aU0?s-UG&1uV
+a8_@Y-Q^JBxp#rlu7g7WyebY&j}OJ;7y+u!5^rtrUIdd9vZ6K2g}T~ZkA@FheRd@N5>aWnYnE%3OWNU
+2hw%-ForT^^jRW58M}b)VnMUnZ3pEJ0pI<_=b36+zNRr6QB1e3N-gUh*#VNvDW#{#}WJFW9XPc}6k(s
+l;`pqa3UOx$1b&rKI_ReSzz>lan9M(2xkX){6nPR(j7)}@NDL$EZxQHBJSxfXPtOerH_78M;ARG!GPM
+5<2bRGt9P6GChwqmSq>+Ol111BQS7J(F8`$X0&A9RW5X_2k>e`$|k$O&;Y!bF8%X-BSRZL`f15c2|i!
+!R*%J(joV%yG+l?-Ojr%*gI8XxQ#OsUpsE;+y28J8|Y0F`q~u4xqj>nqenilLe_$o{4U0qChbnyo;jM
+9Hz2(joyA(HJW
+p3t``u7bfEj?jD$L&&Bak1X^3#5xT8%+UuIVb=HT<#i$>-$)hN1eH3t{M;?Sj6y*5E8L$({thE?=Gck
+&=uf@Er_EwFiF-FfbGd>AKi5$eprl1Al3bBUq_S2vH1@~dHuVuzTjBd`uOnV!nN
+U#tKrWQ1D&uNzYUg(mhJ@D#SBy
+Ze;I_$+R@X*dLFi}OEz99IH46M8Ama@e78yizIM;Q^e9<(CzQLyDu0HdMmyg((ou*N%OYx$PR5@+W;
+}5scy|)v#y*N{j*voiC@}&wD(%imAb~y?5y+mgJKID`!;7rPE(e<|8YKj~Mdiafg3aNGbr;*09_&FjI
+3V%GH=sylq4%9OOE?j7GYz6jf*NWV%RvXI`>=x5oYoVci>Bm2miw_G1ZyK>^8@1r}O-+5OXZL_
+usVnY#5)ytXg$kSVP)ys7vI6XeSj7B&P3S#erLxxqMibINwJD;$5#$=h~>*V-e_K+WIXBt80$Z?`1IQ
+TX;-$PWN7p1+tSMn3lEVeoGZlQ7ygQKv1>TnE44Un+EvgwTGK>fHiu$PDF6B^r+jSS55dzl$=pB_%rj
+Tj1h82UV|Q&=no*7Gp2(GGoQzc|krmU=_dJ4ZA~Fwl|@y0URvO5A?`jH#!uT@*w#syWS!H5y=USISS5^H&0)mr)f&NNM0@
+EoD>Z`4QlJ7-d7p-cT+C%B|Dy+tPR{Pw?E0FGcW!n9)b~xz6c^O4hx1u(i2$-R4`6R(8E<$%jB^cx+l
+IPILM6X2;=DpGSqY
+uT;7%x1%Q=N^>2D=;MKiW%nhDri89h#CeD(zTVryK`;83yw?No%O3Q=HU0OU^-1c1&b=`X!rFA}BR4u
+qM|MRrMD^{NEl#3oo%#+#_a++ZglC)Q_9c#IK!UgSm4KKZN_>o{3s-8MK^w5G9Y}$~nHdm2LlU~qq}8
+V-aOViacU3*{#3vOGW`x+CDc&bcg#<|DjdsHvnu5ZNgb5mBpUaMCLW
+?=k6u~?IB-9x{=Oi9VIMqwW{WI?~tZp*F+17fCW)=8Lr8w0-@(xtP>2^PQcYN~O%Ri;xyn8bJ_seg_|
+1#_xjy;rx$7Srsp`+Gq3LSB-B2xprWJtkZOx3mZfFn?RlGqmx@5iCniRRsEop16YV@-3hW7)n@@y&%m
+)o5jgAMDbfxNiOEp5n5<{=AYc&Bs`N%ia5OLGd!9Ud@(OcFp-lqf?E3`}5c-(VNOc4?A963m679kGLg
+gsyohPXTq%~*-f~N46prJI^A$Mrk_}Dtq*p?i-sw3op?wBA@oDPm!|EY!NrFTBILV+0Af%xmVb1LO9u
+hawX)hfN!+1|AV21zK#&V9WeJ3b^?&H7BDbYLb_k$5q~;7DibHo4omsTUKyLNIx{J&t382|?uUv4Gfw
+s!H=l{EX9pFn|EK3#)gOYy1kBu$!vHcWr9chRYw~DzGWE4>(kT^H8IyIq&R3exi%AqH07sEb*KWquz(
+c}}mapcZDh;O&jtzDZN!iHQ)nAfQ`7*a3p+TnpDdmgkz&&SS8%$oSg8Yp1)R*%mzh*zbgnTCI2i&OB8
+$y*>p(EY$jab)Gr!`Wo>vi^AdWMDWytC{XNhUs7OZ6qnxVOlV7aajId)NmMc(Szl9{5&H(ka%VhJo*H
+1EP2TRvAcJyxxg`bL^PzxsC!1PM?dNdmy{eKUa(~H#n}Okf=R(fHXc$X?ns1q^k8qQ5()41-5qQf7-2
+2IE_xR_JdA@oCVlyY{)+^ScFd8swKDm8khy{L2i$M&ocJijcsBcFmC2#wErQ32#&~YA-~CaN#s4hnkY
+I+0z+*Gpu2{pLrA7Ya=e$^yH+<~;`)uyPNlgbLppb0OWXH@Br1H2{=0-18(yq8$BvO{yqBeQ+B7l(EC
+{2DSI7_LC7PrYkB!x<$p_v0j%ZUB|fytL|GD0!m*?ko2%0gx9y3MDQ7c0rVLnwWJ)`2{G_vK7@`gLgY3=A
+W{QLm}?glod@SLC@SGYKp-S`Dr=WTyXsZr`7Su!kA8Qm^3BbGYEq+)^hOfGd8BbUkv<25%na`HCXf8f$L%(QA#RKsB_eH1hjQXL1;|0)5b0c
+-$mkx)DBIHSwN9|CHT7GISBt5K_!`#k-j7{2G0w0<(D7ug;kh+o$^(aqnilb@Ql5{zoVG1fQCxft$LV
+Po+)1fmsQFB@hX;aG-&37qIafDD_*^`JmQ8vSf{(U#gbMHjo)2$rV=_?mrWpU@(?#{Sl!oBw-O0Is
+rn&It45C|ys8Zq*d&MbE3`M|vD%K;*(H3gPUUg$XJ(ob3F*3f)7mgq2%9JcI+WFo=h_F#u=v`uA7yLb
+N~&oZj@F;4yK-6@w6L1{Ghvs1M#Q3WY?U6s4-=(y`~YKtN}(7sM>1HZleEPB4qU*_=aywFhrx2w^b{got@A%d
+wW}=c1Zhz)y&qf)S2f87`@A;e|a+f_gQ-RXgqEI`r^@)UJPNBOwSEd7z_rlcXHE)ohk{F*fkRRA;~HA
+)nxiwGSl`cC;ZjvE1@N_44gbquTxsc<@6*ycB?tVk($OC@8{;{6hi9~*SR!hDxB51m5LhH*@gK4Bxlb
+xXPQM(;&^ZRc$Hu8%3UpyAGPUd%X8NGdnNsxp6K%)?S)t+;UDkeJVX)tD|H8h>5x%qCTbC3&ooc;*;K
+hC81a2sz9tUT-kyj58gAjvs9_W4l`I%7Vg-D{Un^~Bu#DNBDuGHPeYVS{V@9{;U5@9plhqZtmP9i%({
+Y#6Jn6t}i@s}rN*;&Na5@~H3J=raCq681zaw$m+;a$I53r96q#n<{XLL&DoP}Cj?oDh9=%rG8LCI)1L
++ILcXn=d7puW?HF}K0FS0u<)n0TFnI!hK=Eyo#=o)ZUqnc-WUS^L(bWH24r>BARC$u)JmEX7B}JH!UI
+g#~54b=-?N(br2FzSOx{@(r$nsKu{Ws6=8)GORg$MwLGN_~zxI3m)OdRLFzI!DTCzEeK!w{zG%PH%I~
+(BtEsq7hHKnd&5GG28gz!V={2uM#FCTqbb|zue+f-V~`^6hPvAD(hG-Nrn-aZll<~O+Z?v$F6=a7fN%l%@|)?%nf
+c*==4?5{niWXtx%LHzw_pZ07~8uO{MTk~&N>sypN*^k%utU@dBjMKK-@`D>g?6y2d=z-pw-
+y>m>g*x1xWT+uP57n%5Unw=IUOfpTjSt%$aO&YRrz(4+gBeY(t2ii}i$A#x*C-`D=&3-
+jD9&dEG&EPze~VFS)c#&x%ip7Zma)^9bX;_)`HgQfeCjwjS&xd-0j`7hNp&gdjuHrN13N`uRY<+~J9L
+;{B7?NO(XA7iy?M#EWDIf1sm4L1*tC6CO46npn1k*i%~yPPDy>{PWU30sE~jEQRh)EQsgHXqA(H)`*7
+<7<9~y*7XU;vC(AzEuxxoZzd(u;QARn4SIsA2{|CNE+#03uZ8?5HOsqu!qn|y@>hPmN>*rORpPNp~uG
+h(BU9?2Vr6Cy8Ee{DZfp^Ic(=VqTu_KB=lg?CffUuC)lYQi4}}e7vW~x-cbm3ySpmUna)cipZ+yE>cL
+aakv2F@29~w-xDp1>G9r|zzVQ|{HrdB1Er_#!eexty=kEv42hng+mL4G2&v4t>t{!z)6tW%LE!*rZNp
+@9!P<#naf^^cg$qJ2$;xwh0>xi)q!jVlJjGeF+O_*=FX4q-+oxMXavu@_B1IrDfFZr5mkXiDhHT{X7B
+TK$}{`6aO^CTxBMH-@f-Yz+2Ao-j>IOnpO_tq(ay2mKEH?s^g4wy`*A~rhUL+Vsp)1`&xZV%dOlscWVY~6ZtcmK^
+*GE#moM_ONb=0B_bC&ybqigU581?og^?BT-*Ch=c;P*CC3e)_BxCQcBJFGj6r(g&CAIo0f#x$%dR!U@
+yI%&qgD_=;0G_Syi?o9y6vtaiq~;1l)s#LT*7flD6khNZvY<|6Ws??kxcDzp+OkDdT2Bq9l=!t{Mb~I
+9(f8~K4gr)IsqjgQ)KPf-!i;Q#iCMSAPq(Y714*o!**Kg)Bg6+5Lfzwjq}yM-IIj+EFdT3Hi_i1Cz-w
+Io1lDFBmawCf8WF?W;k@67M^?qrY9H`r(UH*RH}mLTFL`6-E)XJiy5vKr4j7yjAz
+^2c#cAQ4B;R3#7txUM#gGnFF6T3l8&U}c!A8`@-0uPu5IX?u86A?&>lPm2#yd@+k3EuT}IV9T
+ZOH=G+@+cVv1($%+T;xWb!bWXGbCTIyW-YtYHw*;bpgRTbQk8h(2FW}<@=I>8O
+c_2W#?krYBLo4O3<}GwJunAI3}9?YIRp3&SB=2@xp1yqd
+KN4s0FTZ&-d3^dGXW#s1`rzU0Pk;K`^wNY}w9CL5r!?hXok0}*J#ofbY#o_p$?%_RS1B%5O5TcbX2Mj
+N4C#OM3W4>zEs=+X*60w0iQFzj>xbjWZ>rqfYW48sNivetnb`PCVs}&AyyE5#cnCRH?x2CAGMj_^#%b
+(2QMnaV<0-9oSN>$Kv(42a(@_k`@ZrOUCm)9le=+jC-eY3|)b4DaAnVF*l@Q-C%`@0WSZkNGoCPy7?pCv!OdPXRd;|etm+jBOfJVV?&9YQ-YgPDZ&?;`m%m96iFV-ZDd`(44qKfIJ6v9BeDcfEA0s$VRPFd_S6
+WEIf4>k=gb5|Ghg&{X5IV>o%usgUw#e5{GkUi*S`j0zV7M
+EuYs7adl0kA>Q(PD#K&;;c9l2drVfG3Lfa0#nDrWT+Gb3Bb7ey}`A{eC5W5Jtt+2M=SRwb2`DZZ2wv$
+M3=4i;aw8~_rfR(L1K&hFL-%oRf-`*$GMUhyida!&8K5WQG7px5Vp9Fi{3zqY`-vy{f=WQ*WTsoZJp7
+HJp0y3W?)2@a4VD5YA@<1aT#%m9oA3z1tNa>6G?1A?X=%*Pnd+8}GzAL0ck2_trr)^v!}z*~@%UF~?4Btg5=eP$M#$_d99dg)I3i*1_;2*j$;t2VpWptT
+|8w%yzv4e%9iRMdv-oO!hT!TYQms51i@&@V8T(K}o1*^r2F(f&3ma$t-1&1YJFuxCcZ*=#Hu6G?T>w@0?|8shbqL~I=LE)lZY8NM
+*ee>^8*E7f~KK5cL2C~zQx!)qsnI!A&rlAg>{`hDmXdFu?^T<6u^87vXme3#>wn{5AOiy1zAbF~Yva8
+4wZ5Jry^INqJtNU5olrD4t_jnJ-Gm=gi&rUUHh>vMali}x|=4RCFPX9uw7l#LJCT|sQ(oJmSf;+M}S
+dN~*iGi=23b(U{95@y%PdK%U?DOl`;T{I>^E4W~Bhq2L5-a1z@-=>!_Pu0gCqXXeEl(MMvg&+n!?%KH
+ft}E}m$Yayy5(>Pjg;)oEDU<=
+%~7eUM#|&`T8u`m#g`^4QYFVzaf*-gnNNgfo*MxmmKxuc7N6b<(Dri<_x7%#knnh>DH@)CY?e
+&5qP*99DF`4LO;VNdkaSJ^h>HX57|<+17&u=V)%AG{VopY43D3(6&=GJvusHa(XHltzi}>JnnTyMR3P
+jHSDw#j{Dc)DdFaH_;)U$59x|*NAHjHvAf#rK%}$Oa?d*!rnpVT?91(?;uOPPBJFEYhuS=%X|jzWxIW
+{cy_CZjE1iCv^GoM^7Xpac9QtFeTOWix!g*ooz(i+zAxw=(Ge|PkA$WEtS^|)kiQWrz1!PW7kNF;s)dAS3cDbxg&>l&R383(PnU=5?-Nk6z4;o%-+2EuAtFn?Mm>y>}+
+H+QcUGxU>g2gR4bxnVmpRoh2S-X0%-&3Or35yovS$v>c=#oMlE9oI}l@*s%zt`}W8jA+5^4Ae;bBT$B
+#y$bvPqq-W*unzvoh9i4Q$If>yQ()zAeT5gDUpxZ&=9}+=U^DP2Sf2Px-oD$5b<}
+M7ytke0001RX>c!WZ)0I}X>V?GE^v9hSZ#0PxDo#DU%`4mNJecLhXVZ&ASkjM?`?2-S*)|zEz&?MOSH
+|4Eb2%qj(fQK?R{oMy*bHVTNL#dOXSRO=Hbl4p~mC!rA`y4bYaDQD~%L;DRQkJ#9AAX>SC=nb}Ce1U6
+SX*x@x_i3hC1EcsyDgy%TY~u3Tkg91FE8wQ(X@S)Er-#`XJXG#~3Oap^3R}T`3SSlc
+}ax9)50H{m1GJC~VV{81
+La2Trwt8=Znk5?ION9`}7kuuNKSY*^dk81wrtS(Jg_nR@giImwA%POfX5W4n#(@xyUjm1ws%|RVD?6C
+y=h(s3#hYMmI(3bD&Jphh)P>ZI!0d+I5u!c+ZengvGqI6#0T&Li(=cD3dvoClg^+VJkg3sd)jX5&eH
+51%`s)sTD=7=y9YnkP2Rz_E}{_Z=L>Byialqb^~?>!~u30*n
+^F2ViV^~2)aW&D7ri@ja;jzVA2I+x^;O>=Fo&jREvr~ol+an*%_}AE4`iZv?uwB%~5ZFJ@&YCk;H|LH
+@RLV`5$0{vIhdtU=}R2=W%X7ni8?YQiBckY~A>N8)C&T6)Z5I89Bp$_F7jZnPQ?SK=hmUi)zW}!KJ5B|YX7^uDJch5auPoQ+aQ)Z
+zl6NkSkq|M_BS+WmncuG(X1QL|kp-G*BcE^ibP&AmM;#n&hm+P3uHmOtqL;c^E-39gOK-#lTtWQ~_F=Eh10{V+m
+_!^nI&i*Y9&jO9jHl+T^9mT?q;+9C!dY+&!*wKl(x7XEH!`ibtcc>d5u-k?piZyYKyXDW*oA1^+M3Xj
+%^M@Gk`$4Muj0?WitRI$#}esON`phuNZe)~xuL0%w&+Vi18{&EATcKZ=T6VdgWKw2zEZ_JihO5wf^;Q
+GVoN?%vWmMkw<`%C>w8=HOj7o+L&x)AM=$PCTkq{8hsEi^9a#||1=19;JI)T~HZn*Dx)yaoib0K!M$8
+4xD^_c7Rg?>H9aEgE6>5ZA7-q*)wi0o(v%I)n)Kf#cl20Xwfy*oS9hr1oj=%}#zgr8-)Tq)~pN6DB=8
+jcbx1MRd5vxk&nXVkMh7|!!1O)jPbWM6sO}PGB!xgmYM?GKdVvoK44WDU=$w1+ce6BTErA9ucGm$B-?
+vrqlRY5I~%T1CVVvD-MejOp+S+02z$(5m`160^B1eWRT>C=-rxKFqWvxuTU@?&ba$|h&+RGq5~tioPQ@0qeo7a-si5x>_2b|)YJUoN;>
+wlPsxNn$N&kaY^Yx4!7+yDqpRVYs=|j%(Y}BfvJ7%3lwLVb$^|w*8e~mBQ4&ZKUl9;1R%!WYzb!?dz`
+F(sM?#8njnm&qtmaaGB``>)8-qi6cW@@p)P(VNiC!z)QNG7Vv4DN+T)_hrHFwqY)>fgWi$=;y$L#}i4-$$to_iF&LE~aHtD8+~FxiN2;cG#pn73B
+f8Tt+CfG9Kl+Wu>5RvA)Ut0KwOKV_^-9uFI5un>d~^tCC-1XUPpCCwhEIenMnFC63=-A+2s{H;Vqo<5
+sM>H$B;-k#P@^Bg_-IZANM@gL98lb?IVVG8g;(l>(s_tRcm$$sBK1QgNU@f4WW!x9H*xiCHHQn&d+OZ
+`RDcTw5(Tg<+QOCCTPfGU-lq^}v8zDkvqku*zWMNEe0Bd?ky%v9<^FMc{$ecEUahbNu0f7IUhFbK9Xf
+shUEUp+@yx;qvJ;{Ytpt4sz*!$fq)V|9N<$dWanVX<99yDr>6wiWgk($a}g~>1Z?xHnJ*7mTel{NS
+0-UmvtpIp(|Atjb@qs9t7>WWbR4rlzY6VQgE?VL7?Ss9uWBK59O8E@NK{za#m|{Y1Us$S;;veV^T}HE
+h!OIUfnTiu8zsoPTy35{6x;u=f1QxSGv)>P-!QZP~|BZoWtKikX7m-qcXqAfB~`qBhnn$;023h7^TnR
+Ec$!B{|yUa90b9dZOE4CjILG^c_^aT8V9_+Wx$spxW(=`u)ao+1uL1*z%rm0v}A^1W59;oP(`(t5ui;
+-Snujx1{NzgnsKqI!d5nZ>K)1gpkZByW|b8n0dA&sLG`ARWvVolTA_!d@Rr`ueu$vb=Ja&
+tBS`|CCpSSzcl3+JH!tg5^H{|Ppqn3b~a!;6dT;yiR1#^l3?4}heEfZRtCS+kmnH52*HN7|3Hv=1ef48G(n3RBdUh2F}fUGZ#CO
+^wmZ)=_J!TKrK7mOye~>@I$#;ErJgk^(T03f$}N6Aj5t00Ut(zi}iJRkNILI7X^4LOwb@AJf1r%xfxX
+=`N_bFI-E=@NO%?HGRW~q4jz+n~f4Onw^c7Nf$4mAMvN;J)Ywd1UV@8yeP;DDwR@AiGVAI-uUa~FPEt
+^)BN3=wjkHbp!L(AEf`WZAREMO`5hCs>Pd#?YLtvHYsofDg7=t9M`sD)%D~*mdADP9xz&>1f)uqmxEKBGbELh6JfgdrZah)JuBE}&Tod#5F}tHO3g5i}_d#Hr0@4=!H>TiYt!Ad`UxMEm8Bb52pLdd76
+G`zQ8htwbJW4UTsE$~ikhhJ{ykvjbQ}F;0`Y>?LwTsvX%~P*f=CyIe#4ng=@%8EabctqTc_Yu<%^KRJ
+P1k9BJ((_-9Y-@HNiiM8evg7bf%B?rHXHtcZiUs<<~Xh}6?w`LuUUpr>LE=*NqFFIqc^4Nb>!W#7_rE
+l#Ski<8&me6F)WlVESSxa5zz&LIpu;!0EuDT(I_h279Wo)_|^u_zS8wOoV!Q5_gH`$}z~1*Ctw9
+=!4`^ud!QWpAm4@+1I_;8?opF>bY;vY-NHpQfL)6P}%@(A7dOWRHYn7l*^(Gi_$yV!(#T_5`SPhh4?L
+L-9_}h~sDX`kj$z#|bnb$f>xGwLe(8&)60hNM+5;Y_%4lZgV~Q`Mbx=?dRK2{NYKjWxr}93VlZ=pJ*1E$BPQ7gW_g0<+o1)R$=N#vI3B6rL#XJ+g=mI_cUylzr%lpd~
+TFTrsy6{SJM0#RZj|!JxLTz;LYOjbdgNY6lT8&ui;L&GoL;?CG>>ZJX$89zz2*#(!^?6S8XXW%1vJ!z
+5W}{Ou1WIFDx`)97zpt#wo@BFit;!=9>|c2T^RQ|6vwu7df1QPjd%+KX3y0~B*VEkmz7czhcj&oy
+m#O&RKxQYJ!MXM=>%DAP9Q+4RO9KQH000080Pmr;M%%1+_I&^V0Gt2-022TJ0B~t=FLQKZbaiuIV{c?
+-b1rasJ&QpK!Y~j;_j8IIL0h*jY7;S7XC%qQ<)F}lV2Q-!{uadU-hZDrc!hXk}$=E^v9hJ!^N{#RK9}WEXJh^H&o3d)u
+vaD2A)J>A-S+P<{k*ax{eikx>%H{=00A0GP&
+U~nitlximQ_?|52npkQ^8P|Z!CGBo$HX6+afQM)U@6t&28M2F(5HhZGo$S-(3g&u+W<(D~sB6xh(FJJ
+WKzI%lHZ=StM&cQ{NXgGl1qd4nkfok`910s~aR@9oLInjh)|KzKy@R`1a!cMg029cYnTk7r(mv_TsxQ
+Utd@-7Gb^l?8R@I1c+%zAYi6J@I=990o)MR?R-;}3tiVUr2#)+rZlT&YMtEam}Xw-9B{h_6c)(_xh;+
+BCN0}$Mo_ACQ)wOcMv8<@%!pZ$>Y|C^>2P6iuE1bXy{NKHW0Aa~pRWOrYS72d^lkmc?D2S6)ob(z(@=89VrmS7HL=x`@PquizhVfK5*0}0BKB
+A5(2g>mh{risbNkDQ`K<^UN29UglFG9
+)GFj`g+uOSR!gs=1>X={yCht>)F_uA*mq}Zvl(5l9bI#XM%Kw{iysrE-BKrkY+H9%U!Kr>Qp@T0(;SG
+PIv!EMrbIBxM>UY2*b{_RHO5`wtRnp*JtE9{dZ-{HQ{s5x#?Prxr2&N7P1h+TJ}uI>TB943^_bFJ=mR
+fAw9I7Pdj!%tX*lJS74q-8NBxzHqQ;KoLKAh%g_YetjyK%1guCT@%DAFYnmMYM`kbYhhBI$PZ~j73`h
+1R@nTlG)KXoM!dkIJHmU5`!wXKaEbKD8dFXEx;0+aR*sT+0VEd(-Z`E3dB<|Wl+qt5P*FRyd=9`gFF-
+hDHfKf)nV7cW1VK8kA%|W;=pY*-lxDTfccKLyx$F3ihJNUBS464XwKW)Y(WIAfwXyUs7#y|S#G8;N4V
+(Qq#(hu(uJ-_iWO)H;3H&Lm;ua1pxo)*%&n*C`D0r*0~ycH{P3&1nIrhS5qms
+PS_gYF{gZ?!-Zw!yuU%t04I)3O$61*m}LtU!~Ut83b3fOL$^1Px;Y?}pmKqEHJovS{}!AP#Vt`VJWdh
+qSN#H%<=O!J=fd0X_&O=acBiGAn}XF5sDibQ6XaQKaX&njA?d3obP4a6$GKQ5s$n{b^s+x5qT2-%?<^~2DUPaew0!G>NlUyfXf?(oxmeBlPY)%`vwMYHt;TBY0}mLE(fc
+HVd^kae=4`Y=M^nlnk|>GR|~KTWU0`e0k}CUHuoMGQ^4UA5%mR_o(e@FFDFtA`2E}%!V%9r5b8y2kOp
+BG-Rg%lTj{z9!VxYm;DsQ^(hp}}m$2mUa#P_RB`ze70G^Q`Xvz&t^B!zP;XAMImc;eeh@7e|kWknQjJ
+i%sfJFnny5cEW?&P^$pS`%5$vm%LoPFkge0KJ^`|a#vYK&v6gX@RlggIKqWNeCe-8T_FOs4xGBEpD(wukA=ZHKkd=qp?0;W9Zy?{jq6AyXE*?Y@1P_xO
+!sHuZKfxE+o2JZ=bw-cHG8ztC%Kl)^qo`2LXVg*?393VFHad1Q&xG4vX1RVl|$nDvMQ*W*=stQe>T6u
+H?WOYPNvN}I!Z#la7?#;z_?_HZQgy~t5)!I-$%~DWfwDWEZIX&!<3LdTwcxN_tMgWF9jKK8?Z7Z43m?
+PQ+h0Bx`t@dY&D?&gc>#zo4x7(5Wifj@x5Uo4~PTYZ-D^8MXaeJbh#o?hGNsRUiI>W}qU@;hGK{)eB6
+NcSwYM>lseKS%GfQ6e;sxU`5`GPz#^V(mZ-dsPw34M9wSpQpOKrcQ(-vQO80>eY6=kKf5+Vho0IZ7?S
+F3wlw`IHsk6f_?Q&$~MNNuNJgzyG~@aq6slG3*2E3>>bD6^uY+C+x}Qv#P1k+Wn9h94qA9O+zwH#(5_nrr&(w4}=s4{tPh3-n7SLvnj@c)l6uFtfG
+JA+Q_`>|_@g6u*U?UyaaOrKgUZj)jK4u;{#BlW`%Kd5&XU%z?x
+4O&W;H_faXiSmJf$ulF0%@QO$BN{eos7wPl7JfiEyzUBJkkA#=y~n`P5K
+k+7pW&;a??rtvfIEgBHb$ECIh3ZIghLYD`Fc3g!<^625{HLeF7;2VuB(7KjUSyaE1<6&zTbdSd`jl6s
+M4lP-{oca28{j{*atZVwPuKy1OZ*MY??+}{R=cCRbPIlzygnrWASVFK8J5iX(YpaKs1@8IRz-{Ul4KY
+d}oe-S-<83xDr6CZy_@pt$#94t)#^MOK-$2i)r0r(6@`^&|AGvXMv=fdMfdg
+E3nGEo1%ARA9gvQ`l;Fbf&?_Y4cRAN)Bbn5K#vtg%p=%o{|TGzhI?kQwEb`z<3f*tYJoeD~{ky86z-`f3r*F}!Dq9eYhzGF4`MHl@&qA>b)X}a6pBW(weD&8qU0i%S4T-mU#`t1uYDQ>%{ma$7xM?)rKQwvoZ`v0{$(s)IYB49tH5UXdd`KnFjt(roldkMaq1!Kt
+hYfqx2De;H4amFhS{4lo1sP?9+8`-)<*L<9c-PGsRzCyj*
+sn{V_#gX8L=#X1BWeJW<4>59}rBhla0bC3k++#DIAId>H?I*vHr`*KSc$rO#&ePRC#18QJmwcxBb=*Q
+IQ{>?P#WTEu-9EM+VU`FlC0ln3VX8Nt)V0Jkl-laz58?$y4&mK`LS#dWP;SNLm*3%vJQmBcDrjAn+mX
+AK<061g~-LOtWHSLO)?OE(?%-q`P4uDsYMy7paON{h9@^U8@nA(F9
+9#|$%e%jFtl4T=jpfmtahR!!XBHREHp
+X`wMWGMcnH?mS6x^%Eauj%!RRHg)%fvSIW%5V@{Y-)!sc6mJ_5=@z=0P{ixw)4OudFH!3mm?nex!|7+
+sjJ4M1WqJEqZ?3}Mk$cn$>PQ#t_V?TmmDWF?2Lg6SiE|+wiO)LWwbQ?ZU`N{$1ujVMf~EGJLvF*V8Yw`JKD=|nw6O>G
+>$KWa%Q(MxbmQx5Ve4)U>!zlq0`j2RN_K2XtqXfSFX8gaP%Xbk}9$1(-9XOn|P!7y!|NBGWkB&d2oy@sU4C=!{8d;1{7%*OFJI_y0MMiF)(7YQ^2+UoGa
+n%oXY+H!y!x@F!&GQ;{$(ZgW>p?k8i=U71o?%S!OBMPq=_XJOnC3HD*2q6I6S?Ndi5lP
+D{t<`PQZ0fUyzidJw0{UL^315ijN69qgun&OBCIy`=rVeJn$eE5~
+Suf)tS#vl6+m-6wyK(8;jEA=4HAgL#!UdeV#S*c5$bhsH|3uzFcJBDV%)zn-gyW78{P_1ygXW!mxjw1
+LC~}!Jk?@DIs;6)mjN~w)%B#XG8{P;^id|&556j_zNF#z?SuXm-JWJdIM5y$0@!X&14Vifvv%0(b)4O
+)DQ_~e@nx93!1dtnmkSV4mR;Q8Y0=^rV*lC6FY5N`iPdE>syjwCZos<;Xg7}s0ISG0Tw}~
+!gOdJdXERQKdTp*JXRN%y(FTS6&^@)7@ck5WzHP(0T|z?%+DxJeMrwN9@Um8~y=(DrZ#zf*IbkAQ?~%
+Xgy*Zs@#PLWSe{!riBjHT7&GPuS_+;e%*UsGHdK&QLLwf_~Y2ZyYgmxL;Y~hs%ap)_XDWQ?Y};tUJqaX8iN(^pVud}K=cDp@o9h*3u_GYdb#FT8@}L=@KChOfhkhTQV>|~n8mt*FjKnBpxA$+NtGr
+ILXsb9?}!7f9f6jpPiz9=C;l}E!I2Uve4Hp>2t5C7RTe8ciM9l*&}=3t#C|EuRr1flMqlWz@5%uLl$7
+`t)Fd#~+yJZF#jUs8J_ekt&b~fro?8l5!Q26{*9Q8F=oAJwle|x|+?UChh2R2efk@ABn)xuayNH6mnprMEAdEU2!(1}*w
+iWUSZ2)93aBvJ;sF;H(W^>$U6~;7laX%Aapz%$*7{av3n#&~Ws+?h529qGl%__k#AgoPiX=d&=2bw7r
+=Ck91?4i3{2jSTwR#qZ31NMX7lpvASvW08T?%A1}4aZ<@t#Rqee(B(bsF^jb2V7&MOsSHTZdUQ_0NgC
+Ms5c2q3L(?sJw8VaW}Q?E$*wip*V4MN2-EuFwJf;+rvCo-i|?+!e|LemP~W_LfBELStH1vpy28?w1zu
++nU}WYXQ}JCNzUCtlEPiCRxAXKG)e9P$Sq_FgPx40EN)rY0?hW1z1N?u8{zeM}RNWbO{TLq_ap622x>
+;og-8Jx~PhhuvZL5&H<^WE&I7Z8Z2)tR7H$4zS__0yeg+aKR65=>dJdxp?*E_uszvr
+tsb~$L1REn_%Xce{6`eGgCAZ_NVHNIYw@|Vv=pdw%|k<^ckZ~xe2E526Qx?$Wco?b+(N=N%xb%Ho%`Fg2{pj|Nq7TFp8QO`WsorZRaz%Xd=n1Kx+{6DE^Kj_CHI2jKQi-Qr&W?&+P>Q`VXxkZ@lMeZp
+&0#EBHSc`#|AAEiG{AT8%9R6aQA2Zgcd&YXiIHoCZn9j9$Tzq@Z$xmE_K~%~xhsFiEW&v~O2Np2P#Uv
+zFh=1@Q4L(F4((omlcz2=mc5QD4*-Pruxq*6pCuuJpv+m(fze%4_N4o!SajXwoSa)Q!|Ck>-yY(PvjE
+w%j&mx^!kMc?f4Q84S5kAU6K7io(L&~hgjAS8=a8z&LgREskvd$#1*r1I3_dpm7UPj=wd3%U3a&N(Q@({p``)8f5gG?*T8>NU
+CbdC5&V29}{4$4_IP5DRQzz&bqt8$A=!9YhYIFws<$DL(^l9M0LQ^4@r?~}N#jqFYE|Q9pu4RX-m0-KfS++_re!V|EQ>uoWtpj+=sg5BBmh38^
+sere1M}HsI!_6VY-NhKG14$!aDd)?^X80A=T#~9sQEthT;t8;+7D4s2-h(|hl!6Z58;sw=pjWyfnun?Z>0ER90?8GPV*o`rX=8%gy@oPgM(7nl!Aqs&`exOl%@b3ZhnK
+A@oJ@0~Gn&z{iNnNnUn#KSICOVfj3{y}Gst;nH8XicoUj~CO5?!SV?EbG9~1W
+$AFIlbDe0sy}5b86~pRAu1Ce*)eHQX2*tVA`+C};==3J)-z;*ynifr-b+Q4e-ptGk;hkw@+1rzBew7-
+&xji^+@UIrZHeKAsnEeb4KhFT}E)Wni<(;`(#=uMHHQrn=s~Q7=m}XtmT@M$GMHay*A_L8PJR}VA7F;
+^Hw^r#Tous+ALDFM83~x*^2)9mt#4|XJc~b-?xy4dXRQxJI_LKE#H^gR3>*Ixm=sb
+&xxs9p&@7dFOd&^jwh>t@Jk1n_zELIBEyq|F&XcyUZC2NR<#OHB1!4dxWQ#<|SYYaD^sk?z#f|7|%hM
+c1>rAP^9UiU25ECXP$SNX4Cs*QQ?^?u<(#9b6L(+pMyCp@vy(xvOF(=J<$^=#SGi%`pjuqV!a1$2C<{
+u#hiiNo~TX-OK{(G8i8c%)Bquu$US=|Jf)_>;57hDph5Rtpdjn2%wO~Y3V&(NoQ+#fu?i}+c8Wr9F7@Je;WH;
+B8YT3E0BBxMvp#}rQ2%bB-yIX_Xx^9gRUfcC=?SAsoBYMCWdg(~XynIQIHal!@J16tZ{lL>o`eosKz_
+Nb-@^1Qubk*0BuI9?`6QLjeGg5DF73#U(Uc0H6<=sZ3d6=bRhfp}Pb+E
+PlR;V+(mu_n!Yqn{N!?p}Q_}fkMp669aZW5pF8;V~<}>s9OS@51Ug1dFin3UOINE=`lJ<$DXb~l3G;2
+o-M$b#1pyJIys)Gv?WQ*SQl99Z6)a}UjN`Ri7C&2bK09z-(c{uhIa>%CIH0JCJA#0gI)s^XF+tk!xk9
+t2rmwF!_Bg1KzP&b=n?5l@miW>g~E3(xvjOifCspA(p>Gb$5@e6{K^iC1P?Q{o2j3;tjPm=jUBQK9PF
+KKM3cuyKe>K5v3@uRxAd^A4DW43tba$y-+`WCjR|`-*E4wLMa_+K
+&l@32sVbQ9(XDmy(RXb?pdWC&H+*mP3O4MS?^tQ4zL`JJoNb>|`-2Pa`uqhaOd)%X|k~cD@~dyeKDPE
+2C10I=Z@zS%T%_uCso^2ChnN)TMxfh(zXcNz{yf2McDCgI0%8rV8=gS$(muxBG+9Vc|p`=$atw{<|
+-RC&kwX?Wm^!GuHgX)D(>2E0>P^pXNkmo;92jx!M)1Md6IWpbplj;LCPP1%#Y5Kz62^#$>78<-Ir
+2ROe&1^;6!pqoeR>&`%2AmJ9O!T8QR?k+{LYp04%gyotczX^a
+2T;l}9na)wsNJ!OU%3&fWRxH{QjjK2OPNnU2gmvJ7rXo~i4pH#FJCsp
+);AK6HrkY(!M<7YDuK_JPA-rWV`uoM>sIj57>sZKb$@+_oR%R$K1}KVw`k6rxC5FnvYx&i!qNj}q7$O
+5%O^1lgTn2%{%$QyQ3Kr2%}+J*@T4f5G4kFR*cy1mQ}rShKA-?P_XpXr;S_xzI&N>|<~j;K4w)6s&(_
+;?!YZkOHPKh_JZUX
+;Y&?U*+k()C?UYdn-tRi1Y7}+gb*8y+_v>!50^&!9wFrHn6OqS;b!WW>(22N5f$Co*!%q+V-r{R_Z1+Z;^0)qnt8?((1DYts7w~??-ui5=(yCsqxL6%UyI^lHG(Ik(y
+1&+^+PgeQ>YGy6cz{Kzt$9Wz!A+e0SS}IS$qs^YLwN4SHU^%kgnUt|Z9f=I@704#VON3~+G&rjp7KP4-D
+4|Fd^Zj6gPJDgabBWt39vrO{}eXm_0oKU`2b^>m=qd3&ig{iUP0@R)~M1@7K~P!S&6|*J1b>8Tl6qBInGgVX5%G4KT9;Yn%bRiW
+P^Pq;a`9z@}%W3_7@|zHQudWe*{dEj<9^*mTyV!?$CtA4P8bP(|M}dxvoi?ZGTeTafMxDvQPZ(lL?tO
+PV9PT0(AK)SEk~-6xEAy$PVkItu0epw@j*>aNLk_EUz6Lq
+RQ%-l03h>69I^&f35l*GmA@aee|#}`oT9e+yH^^t6Am~i@9@+2Z}$KButVaSAkjI|mt<;#2~P#`KR*5
+h=k5px+LcN<08!RSb*HPzevxYvhP^*mV?7cVl}6v>pd|*zG!~e&>libvJ=O+~TuV^}HUL}^hGdn1Y&u
+rv?C!28x5Z%;?y-E^v9RS51%GHW0n*R}fk()@~f#dI^w2nrzSjMGisH9D;>FOJj
+>MC8{K~j{kk%kdiD}3X%ZT!6wDwaOUyNhtf1nTGeLxGc~2LLOF53U!1AtC`xKErlwHjggFQZ3vWbg2{
+K+ZL?w&G*GZHXrV-B6CrSVQsLg&D8<(}xSHsj@
+T#_Vr;*SepImk}?ozTXd;E{l@PL#@0;f=in5T)36O;NF<*}2l<5WGKE3h&M&&Pr=2lV$M0R|KDqs%aQ
+AY{6mSaJ+>w0N_2;yU4^A|f0CFb`)03Mwo_J#C2_uDb2ZwcnC``ONFu8cd*h4txhD~(O$_W0Q{
+bC6A~sGs5&cGv7Q23J+Xnx3g{A_D0^|K{Bt
+(;6k~uRHy(;y#Ji_$!Cg$QpoJVWu%kHMHm`|OEXahra=hKfPX!knU&e>GKSE*>RJzj>-rbo^YjC&3zUc+
+#w+3Hbocf$Kk80=W)J$>D4T*rg|ZI#3)$iBxClz*WKPkjnVh!ABb2*Q6^#|l71Mc9DIaQ~ghNFas=pv
++!KfX#d#%7X$|iZ5i;8F4TxeSu7V2H)cLr?DRe4|>M@gEV8PF9$0DH)PaUG*N{Cf!rZ>(6cY_
+z3dN=iMyV&)#c$4=n5DHUBehYyf`qwL@nTjEebE#hXeJ4KAOD@QWdoIk%hhB~OHA5t%*fTSvmMHV+lr
+*g~{b%1=xi}Zj+?vgn+K-HZf#gQ=4hh*1EJNCe1)6%yK2p!8Ztd*UJ&dD1)dEgS&her)#fd*%c
+pNVSml!6eV-6wQ7V1-YVa+k_ifSzV+aVQ%HMnLk{YE4-h&+K${{c`-0|XQR000O8@1eFvChl_;F*X1I
+nHd297ytkOaA|NaUv_0~WN&gWX>eg=WO8M5b1ras?R{%^+eWhBcm0ZvbUh?(iXthRmU%7DY8-o_b24^
+r;>qk;uZIUDK?!3L;1Hl5v$Ol#x2pPqeuAJRJ8^E16IlYftE;Q4tE;Q4st?$!Y<0U%E-nk!?{=TDKWE
+v+B4+35w6k}>*!RgaPV;!iij4j6(;K!)XYravP%ACg$z)SxYj$?Aj^kyV7M;Dl{r&wjHjnb6%@zpsIw
+sKPKU@ut;Kz_HqcmA<7EzI8DHNHkqxCIIQuf1bahat6;r(TjmmhMr5v@}njf?AS{VC^o?d{Fi*^-UN^G&f?$Kx?emaA-CK%bInHjBrLq=?tiBH!B^kFVl&4j4kA
+Q`YEpx}9f@y}dnAAuraOX#sy903DVq=*uL5%C~I3NvGJcd~ZCZ591^)#>MR_&H+vn{%E)MiuLVikHHf
+x!xmY3(b|h|rtzv^Z=+@WdcDrpyw)npa~B4n1hqv^0yWLD@RQyGxET60xh(*JZ}|IU&L)lj-EDm6eq^
+VotUF@tK#UMOW_b+sFOq3)KqUaI?B~c2j*(y6ED6c*ts
+T-5zqM=Hxi6^@1Z|cmI=nVoksUrX;d6MQuluqO3d7JC9sXc)j;NwE;HyWUwb
+VmUG_U;;cFrx(Sg+fx@QStEd0HCcc)c}w_uQ1&0*bGYmU>oTB|`0M2<>$As?*>T^b)jCS@Sj^;3j04)
+-hbr*}h_`H0uxt)R(ufTo4NJK-pNT_;`cTy&X=nNEer9CCQP%1a^*70BbY~A&cU!2V@{WbV3z}|N)xdB#@Y$$`GY+HhS}RY|KaDs_pjgn`TchQ<+zW357pnm
+B!I$n0aJ&a@g?x*XtCjfs#*Rj5TQky-ZFX(ynt&dEwjaCELYJEEt&%C+qhUp`KSAUO*sew773QD#-gp
+J__qZ0KKcZhq_D0dk0$BG5ikX<^n!EYTQ>PeJSCP#_|t5?#y?S(z><$}&ej*2j1+B**qHz+<{QqPHi3
+Y~Wak-soxrO#*D$iRHVI8t15}z@Hi0R*S*;dHJo`@x64V$JU%<4$rjH*V_t|x_SZHu2h;y1Jpi*%vO@
+ux(xy6Mg+AIpuj~}VaT360eDk%mV`+-QQKZ!1~E>1|TYB_Erls?$)$D%WjNxJ}Ck{st)%n1-NA^`q7)
+UX$I_T+%kziNHeM2v>08fGpy_`2G3Shnr5o3?I}22|DB@bk2WqPkqg1nf4~oLaU3bf=*q#T$>-Y?cA@
+q#*60)hdqGf&rpba-6|u9o8@be4EVJZMGr&P!L8!g=?aErxa?|Z!ttUj%F}TED%sfXpDkm{@7rTm?GL
+<*&D4JJ7W$o{W@IPkQ4?&VJ~Tm({xzF%+RUtTf@?6yh=Z=Cw5My_#&|&Y^5{R
+G&F8%B>%VDpP6gli7Bp%yj$6G&Fd}bN)d0V~5v=ZdpVOk;Et>;lGw0`-y^i{30=aPPM473+tY(nuSKs
+nZ1cu&;4`eTU@X=^~PS)5N{w@QPn)B;>3R-k|;)6F6(X)RbCSez8K+XiUUlPFK7(PD86RY0u;jhCBjf
+D0xWsKnSA0k<4^U>hlE1Rgb~__@QOpLcS9uJ-ORt<@y0JF%|@eCM2vaq||>+{yi8yn+`oO=wvgw%2UH
+cnzU3fU+P(H0*3H4*AZz1(?B<*)qjbISqljqA|rMvWMDq56m-UV+lv%DNrPI>*HB91<&~ulno^?DDee
+gdqwlQ$THaFtzqLV$H$aKo_&T$Z%^5n&+1jJVLHly{`jMS;{u%5mwiw=(EqcQ*$rr